mirror of
https://github.com/pgpainless/pgpainless.git
synced 2024-12-22 19:08:00 +01:00
Relax constraints on decryption keys to improve interop with faulty, broken legacy clients that have been very naughty and need punishment
This commit is contained in:
parent
d5f3dc80bc
commit
0cb0885251
3 changed files with 68 additions and 5 deletions
|
@ -43,15 +43,14 @@ import org.bouncycastle.openpgp.operator.SessionKeyDataDecryptorFactory;
|
||||||
import org.bouncycastle.util.io.TeeInputStream;
|
import org.bouncycastle.util.io.TeeInputStream;
|
||||||
import org.pgpainless.PGPainless;
|
import org.pgpainless.PGPainless;
|
||||||
import org.pgpainless.algorithm.CompressionAlgorithm;
|
import org.pgpainless.algorithm.CompressionAlgorithm;
|
||||||
import org.pgpainless.algorithm.EncryptionPurpose;
|
|
||||||
import org.pgpainless.algorithm.OpenPgpPacket;
|
import org.pgpainless.algorithm.OpenPgpPacket;
|
||||||
import org.pgpainless.algorithm.StreamEncoding;
|
import org.pgpainless.algorithm.StreamEncoding;
|
||||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
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.InputSymbol;
|
||||||
import org.pgpainless.decryption_verification.syntax_check.PDA;
|
import org.pgpainless.decryption_verification.syntax_check.PDA;
|
||||||
import org.pgpainless.decryption_verification.syntax_check.StackSymbol;
|
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.MalformedOpenPgpMessageException;
|
||||||
import org.pgpainless.exception.MessageNotIntegrityProtectedException;
|
import org.pgpainless.exception.MessageNotIntegrityProtectedException;
|
||||||
import org.pgpainless.exception.MissingDecryptionMethodException;
|
import org.pgpainless.exception.MissingDecryptionMethodException;
|
||||||
|
@ -674,7 +673,7 @@ public class OpenPgpMessageInputStream extends DecryptionStream {
|
||||||
|
|
||||||
for (PGPSecretKeyRing secretKeys : options.getDecryptionKeys()) {
|
for (PGPSecretKeyRing secretKeys : options.getDecryptionKeys()) {
|
||||||
KeyRingInfo info = PGPainless.inspectKeyRing(secretKeys);
|
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())) {
|
if (publicKey.getAlgorithm() == algorithm && info.isSecretKeyAvailable(publicKey.getKeyID())) {
|
||||||
PGPSecretKey candidate = secretKeys.getSecretKey(publicKey.getKeyID());
|
PGPSecretKey candidate = secretKeys.getSecretKey(publicKey.getKeyID());
|
||||||
decryptionKeyCandidates.add(new Tuple<>(secretKeys, candidate));
|
decryptionKeyCandidates.add(new Tuple<>(secretKeys, candidate));
|
||||||
|
@ -692,7 +691,7 @@ public class OpenPgpMessageInputStream extends DecryptionStream {
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyRingInfo info = new KeyRingInfo(secretKeys, policy, new Date());
|
KeyRingInfo info = new KeyRingInfo(secretKeys, policy, new Date());
|
||||||
List<PGPPublicKey> encryptionKeys = info.getEncryptionSubkeys(EncryptionPurpose.ANY);
|
List<PGPPublicKey> encryptionKeys = info.getDecryptionSubkeys();
|
||||||
for (PGPPublicKey key : encryptionKeys) {
|
for (PGPPublicKey key : encryptionKeys) {
|
||||||
if (key.getKeyID() == keyID) {
|
if (key.getKeyID() == keyID) {
|
||||||
return secretKeys;
|
return secretKeys;
|
||||||
|
|
|
@ -41,11 +41,15 @@ import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
||||||
import org.pgpainless.exception.KeyException;
|
import org.pgpainless.exception.KeyException;
|
||||||
import org.pgpainless.key.OpenPgpFingerprint;
|
import org.pgpainless.key.OpenPgpFingerprint;
|
||||||
import org.pgpainless.key.SubkeyIdentifier;
|
import org.pgpainless.key.SubkeyIdentifier;
|
||||||
|
import org.pgpainless.key.util.KeyIdUtil;
|
||||||
import org.pgpainless.key.util.RevocationAttributes;
|
import org.pgpainless.key.util.RevocationAttributes;
|
||||||
import org.pgpainless.policy.Policy;
|
import org.pgpainless.policy.Policy;
|
||||||
import org.pgpainless.signature.SignatureUtils;
|
import org.pgpainless.signature.SignatureUtils;
|
||||||
import org.pgpainless.signature.consumer.SignaturePicker;
|
import org.pgpainless.signature.consumer.SignaturePicker;
|
||||||
import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil;
|
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}.
|
* 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_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 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 PGPKeyRing keys;
|
||||||
private final Signatures signatures;
|
private final Signatures signatures;
|
||||||
private final Date referenceDate;
|
private final Date referenceDate;
|
||||||
|
@ -904,6 +910,7 @@ public class KeyRingInfo {
|
||||||
public @Nonnull List<PGPPublicKey> getEncryptionSubkeys(EncryptionPurpose purpose) {
|
public @Nonnull List<PGPPublicKey> getEncryptionSubkeys(EncryptionPurpose purpose) {
|
||||||
Date primaryExpiration = getPrimaryKeyExpirationDate();
|
Date primaryExpiration = getPrimaryKeyExpirationDate();
|
||||||
if (primaryExpiration != null && primaryExpiration.before(referenceDate)) {
|
if (primaryExpiration != null && primaryExpiration.before(referenceDate)) {
|
||||||
|
LOGGER.debug("Certificate is expired: Primary key is expired on " + DateUtil.formatUTCDate(primaryExpiration));
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -913,15 +920,18 @@ public class KeyRingInfo {
|
||||||
PGPPublicKey subKey = subkeys.next();
|
PGPPublicKey subKey = subkeys.next();
|
||||||
|
|
||||||
if (!isKeyValidlyBound(subKey.getKeyID())) {
|
if (!isKeyValidlyBound(subKey.getKeyID())) {
|
||||||
|
LOGGER.debug("(Sub?)-Key " + KeyIdUtil.formatKeyId(subKey.getKeyID()) + " is not validly bound.");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
Date subkeyExpiration = getSubkeyExpirationDate(OpenPgpFingerprint.of(subKey));
|
Date subkeyExpiration = getSubkeyExpirationDate(OpenPgpFingerprint.of(subKey));
|
||||||
if (subkeyExpiration != null && subkeyExpiration.before(referenceDate)) {
|
if (subkeyExpiration != null && subkeyExpiration.before(referenceDate)) {
|
||||||
|
LOGGER.debug("(Sub?)-Key " + KeyIdUtil.formatKeyId(subKey.getKeyID()) + " is expired on " + DateUtil.formatUTCDate(subkeyExpiration));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!subKey.isEncryptionKey()) {
|
if (!subKey.isEncryptionKey()) {
|
||||||
|
LOGGER.debug("(Sub?)-Key " + KeyIdUtil.formatKeyId(subKey.getKeyID()) + " algorithm is not capable of encryption.");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -947,6 +957,42 @@ public class KeyRingInfo {
|
||||||
return encryptionKeys;
|
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.
|
* Return a list of all keys which carry the provided key flag in their signature.
|
||||||
*
|
*
|
||||||
|
|
|
@ -14,6 +14,7 @@ import java.nio.charset.StandardCharsets;
|
||||||
import org.bouncycastle.openpgp.PGPException;
|
import org.bouncycastle.openpgp.PGPException;
|
||||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||||
import org.bouncycastle.util.io.Streams;
|
import org.bouncycastle.util.io.Streams;
|
||||||
|
import org.junit.jupiter.api.Disabled;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.pgpainless.PGPainless;
|
import org.pgpainless.PGPainless;
|
||||||
import org.pgpainless.exception.MissingDecryptionMethodException;
|
import org.pgpainless.exception.MissingDecryptionMethodException;
|
||||||
|
@ -189,6 +190,23 @@ public class PreventDecryptionUsingNonEncryptionKeyTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@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 {
|
public void nonEncryptionKeyCannotDecrypt() throws IOException {
|
||||||
PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(ENCRYPTION_INCAPABLE_KEY);
|
PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(ENCRYPTION_INCAPABLE_KEY);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue