mirror of
https://github.com/pgpainless/pgpainless.git
synced 2025-09-15 13:19:38 +02:00
Compare commits
80 commits
Author | SHA1 | Date | |
---|---|---|---|
b216fd9d10 | |||
e2fc9ccb9c | |||
cdef935974 | |||
c20e80f716 | |||
dfb7d068bd | |||
3e120fbf7f | |||
25fd3fa1d6 | |||
b74e7bcb7c | |||
3b9159e632 | |||
b5bd4875ae | |||
54ed7a5387 | |||
489419459a | |||
220f186336 | |||
4b8367dcca | |||
09aaa9fa4e | |||
1be4d8e5c4 | |||
d94fa6b74b | |||
d5fcb3bc29 | |||
6716e363c7 | |||
291f59b9e4 | |||
a0c8de57e9 | |||
3c94f3677f | |||
a9f67c508f | |||
c4e937c0f9 | |||
0feafbf7ed | |||
4aaecb7df8 | |||
d8ff266406 | |||
651ca93f90 | |||
08ceee9243 | |||
160a5fe8ff | |||
4c6edc067b | |||
2a4bc03555 | |||
9ec4c47309 | |||
09d036f17b | |||
7aaeb8ccfd | |||
bb1b154745 | |||
9dbee67304 | |||
f67f1954e7 | |||
789a0086c9 | |||
8fb8aa1a30 | |||
0d62cea8a5 | |||
a60917549d | |||
02c68460a9 | |||
24ed479b87 | |||
0f5103577e | |||
f15040dae0 | |||
0f768a7258 | |||
5c61559647 | |||
0a7c76a2dd | |||
6324455cf5 | |||
513ab0e3ed | |||
8a9ebdbb3e | |||
da6582e1d3 | |||
78e607ab85 | |||
f4ce669d44 | |||
ef310f201f | |||
c65e484bb4 | |||
37dc362cc3 | |||
b31d72bd65 | |||
9eea028cf7 | |||
17f90eb721 | |||
73cdf34b02 | |||
6809a490c1 | |||
be8439532d | |||
fe767389a0 | |||
9ba3fcd8b0 | |||
c40a7976e2 | |||
f614c325cb | |||
7cb22f1530 | |||
714e424eac | |||
a3957d3372 | |||
61949240b3 | |||
ef33d9d584 | |||
3ac17281ea | |||
8625b2086c | |||
80e98a02ac | |||
8d6db322a1 | |||
6233ac61e6 | |||
60d6289c4d | |||
7b76a9162d |
75 changed files with 4931 additions and 1151 deletions
29
CHANGELOG.md
29
CHANGELOG.md
|
@ -5,6 +5,35 @@ SPDX-License-Identifier: CC0-1.0
|
|||
|
||||
# PGPainless Changelog
|
||||
|
||||
## 1.4.0-rc1
|
||||
- Reimplement message consumption via new `OpenPgpMessageInputStream`
|
||||
- Fix validation of prepended signatures (#314)
|
||||
- Fix validation of nested signatures (#319)
|
||||
- Reject malformed messages (#237)
|
||||
- Utilize new `PDA` syntax verifier class
|
||||
- Allow for custom message syntax via `Syntax` class
|
||||
- Gracefully handle `UnsupportedPacketVersionException` for signatures
|
||||
- Allow plugin decryption code (e.g. to add support for hardware-backed keys (see #318))
|
||||
- Add `HardwareSecurity` utility class
|
||||
- Add `GnuPGDummyKeyUtil` which can be used to mimic GnuPGs proprietary S2K extensions
|
||||
for keys which were placed on hardware tokens
|
||||
- Add `OpenPgpPacket` enum class to enumerate available packet tags
|
||||
- Remove old decryption classes in favor of new implementation
|
||||
- Removed `DecryptionStream` class and replaced with new abstract class
|
||||
- Removed `DecryptionStreamFactory`
|
||||
- Removed `FinalIOException`
|
||||
- Removed `MissingLiteralDataException` (replaced by `MalformedOpenPgpMessageException`)
|
||||
- Introduce `MessageMetadata` class as potential future replacement for `OpenPgpMetadata`.
|
||||
- can be obtained via `((OpenPgpMessageInputStream) decryptionStream).getMetadata();`
|
||||
- Add `CachingBcPublicKeyDataDecryptorFactory` which can be extended to prevent costly decryption
|
||||
of session keys
|
||||
- Fix: Only verify message integrity once
|
||||
- Remove unnecessary `@throws` declarations on `KeyRingReader` methods
|
||||
- Remove unnecessary `@throws` declarations on `KeyRingUtils` methods
|
||||
- Add `KeyIdUtil.formatKeyId(long id)` to format hexadecimal key-ids.
|
||||
- Add `KeyRingUtils.publicKeys(PGPKeyRing keys)`
|
||||
- Remove `BCUtil` class
|
||||
|
||||
## 1.3.8
|
||||
- Bump `bcprov` to `1.72`
|
||||
- Bump `bcpg` to `1.72.1`
|
||||
|
|
|
@ -191,7 +191,7 @@ repositories {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'org.pgpainless:pgpainless-core:1.3.8'
|
||||
implementation 'org.pgpainless:pgpainless-core:1.4.0-rc1'
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.bouncycastle;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPrivateKey;
|
||||
import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
|
||||
import org.bouncycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory;
|
||||
import org.bouncycastle.util.encoders.Base64;
|
||||
import org.bouncycastle.util.encoders.Hex;
|
||||
import org.pgpainless.decryption_verification.CustomPublicKeyDataDecryptorFactory;
|
||||
import org.pgpainless.key.SubkeyIdentifier;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Implementation of the {@link PublicKeyDataDecryptorFactory} which caches decrypted session keys.
|
||||
* That way, if a message needs to be decrypted multiple times, expensive private key operations can be omitted.
|
||||
*
|
||||
* This implementation changes the behavior or {@link #recoverSessionData(int, byte[][])} to first return any
|
||||
* cache hits.
|
||||
* If no hit is found, the method call is delegated to the underlying {@link PublicKeyDataDecryptorFactory}.
|
||||
* The result of that is then placed in the cache and returned.
|
||||
*/
|
||||
public class CachingBcPublicKeyDataDecryptorFactory
|
||||
extends BcPublicKeyDataDecryptorFactory
|
||||
implements CustomPublicKeyDataDecryptorFactory {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(CachingBcPublicKeyDataDecryptorFactory.class);
|
||||
|
||||
private final Map<String, byte[]> cachedSessionKeys = new HashMap<>();
|
||||
private final SubkeyIdentifier decryptionKey;
|
||||
|
||||
public CachingBcPublicKeyDataDecryptorFactory(PGPPrivateKey privateKey, SubkeyIdentifier decryptionKey) {
|
||||
super(privateKey);
|
||||
this.decryptionKey = decryptionKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] recoverSessionData(int keyAlgorithm, byte[][] secKeyData) throws PGPException {
|
||||
byte[] sessionKey = lookupSessionKeyData(secKeyData);
|
||||
if (sessionKey == null) {
|
||||
LOGGER.debug("Cache miss for encrypted session key " + Hex.toHexString(secKeyData[0]));
|
||||
sessionKey = costlyRecoverSessionData(keyAlgorithm, secKeyData);
|
||||
cacheSessionKeyData(secKeyData, sessionKey);
|
||||
} else {
|
||||
LOGGER.debug("Cache hit for encrypted session key " + Hex.toHexString(secKeyData[0]));
|
||||
}
|
||||
return sessionKey;
|
||||
}
|
||||
|
||||
public byte[] costlyRecoverSessionData(int keyAlgorithm, byte[][] secKeyData) throws PGPException {
|
||||
return super.recoverSessionData(keyAlgorithm, secKeyData);
|
||||
}
|
||||
|
||||
private byte[] lookupSessionKeyData(byte[][] secKeyData) {
|
||||
byte[] sk = secKeyData[0];
|
||||
String key = Base64.toBase64String(sk);
|
||||
byte[] sessionKey = cachedSessionKeys.get(key);
|
||||
return copy(sessionKey);
|
||||
}
|
||||
|
||||
private void cacheSessionKeyData(byte[][] secKeyData, byte[] sessionKey) {
|
||||
byte[] sk = secKeyData[0];
|
||||
String key = Base64.toBase64String(sk);
|
||||
cachedSessionKeys.put(key, copy(sessionKey));
|
||||
}
|
||||
|
||||
private static byte[] copy(byte[] bytes) {
|
||||
if (bytes == null) {
|
||||
return null;
|
||||
}
|
||||
byte[] copy = new byte[bytes.length];
|
||||
System.arraycopy(bytes, 0, copy, 0, copy.length);
|
||||
return copy;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
cachedSessionKeys.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SubkeyIdentifier getSubkeyIdentifier() {
|
||||
return decryptionKey;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/**
|
||||
* Classes which could be upstreamed to BC at some point.
|
||||
*/
|
||||
package org.bouncycastle;
|
|
@ -0,0 +1,31 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.gnupg;
|
||||
|
||||
import org.bouncycastle.bcpg.S2K;
|
||||
|
||||
public enum GnuPGDummyExtension {
|
||||
|
||||
/**
|
||||
* Do not store the secret part at all.
|
||||
*/
|
||||
NO_PRIVATE_KEY(S2K.GNU_PROTECTION_MODE_NO_PRIVATE_KEY),
|
||||
|
||||
/**
|
||||
* A stub to access smartcards.
|
||||
*/
|
||||
DIVERT_TO_CARD(S2K.GNU_PROTECTION_MODE_DIVERT_TO_CARD),
|
||||
;
|
||||
|
||||
private final int id;
|
||||
|
||||
GnuPGDummyExtension(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
}
|
209
pgpainless-core/src/main/java/org/gnupg/GnuPGDummyKeyUtil.java
Normal file
209
pgpainless-core/src/main/java/org/gnupg/GnuPGDummyKeyUtil.java
Normal file
|
@ -0,0 +1,209 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.gnupg;
|
||||
|
||||
import org.bouncycastle.bcpg.PublicKeyPacket;
|
||||
import org.bouncycastle.bcpg.S2K;
|
||||
import org.bouncycastle.bcpg.SecretKeyPacket;
|
||||
import org.bouncycastle.bcpg.SecretSubkeyPacket;
|
||||
import org.bouncycastle.openpgp.PGPSecretKey;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.pgpainless.key.SubkeyIdentifier;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* This class can be used to remove private keys from secret software-keys by replacing them with
|
||||
* stub secret keys in the style of GnuPGs proprietary extensions.
|
||||
*
|
||||
* @see <a href="https://git.gnupg.org/cgi-bin/gitweb.cgi?p=gnupg.git;a=blob;f=doc/DETAILS;hb=HEAD#l1489">
|
||||
* GnuPGs doc/DETAILS - GNU extensions to the S2K algorithm</a>
|
||||
*/
|
||||
public final class GnuPGDummyKeyUtil {
|
||||
|
||||
private GnuPGDummyKeyUtil() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the key-ids of all keys which appear to be stored on a hardware token / smartcard by GnuPG.
|
||||
* Note, that this functionality is based on GnuPGs proprietary S2K extensions, which are not strictly required
|
||||
* for dealing with hardware-backed keys.
|
||||
*
|
||||
* @param secretKeys secret keys
|
||||
* @return set of keys with S2K type GNU_DUMMY_S2K and protection mode DIVERT_TO_CARD
|
||||
*/
|
||||
public static Set<SubkeyIdentifier> getIdsOfKeysWithGnuPGS2KDivertedToCard(PGPSecretKeyRing secretKeys) {
|
||||
Set<SubkeyIdentifier> hardwareBackedKeys = new HashSet<>();
|
||||
for (PGPSecretKey secretKey : secretKeys) {
|
||||
S2K s2K = secretKey.getS2K();
|
||||
if (s2K == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int type = s2K.getType();
|
||||
int mode = s2K.getProtectionMode();
|
||||
// TODO: Is GNU_DUMMY_S2K appropriate?
|
||||
if (type == S2K.GNU_DUMMY_S2K && mode == S2K.GNU_PROTECTION_MODE_DIVERT_TO_CARD) {
|
||||
SubkeyIdentifier hardwareBackedKey = new SubkeyIdentifier(secretKeys, secretKey.getKeyID());
|
||||
hardwareBackedKeys.add(hardwareBackedKey);
|
||||
}
|
||||
}
|
||||
return hardwareBackedKeys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify the given {@link PGPSecretKeyRing}.
|
||||
*
|
||||
* @param secretKeys secret keys
|
||||
* @return builder
|
||||
*/
|
||||
public static Builder modify(PGPSecretKeyRing secretKeys) {
|
||||
return new Builder(secretKeys);
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
|
||||
private final PGPSecretKeyRing keys;
|
||||
|
||||
private Builder(PGPSecretKeyRing keys) {
|
||||
this.keys = keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all private keys that match the given {@link KeyFilter} from the key ring and replace them with
|
||||
* GNU_DUMMY keys with S2K protection mode {@link GnuPGDummyExtension#NO_PRIVATE_KEY}.
|
||||
*
|
||||
* @param filter filter to select keys for removal
|
||||
* @return modified key ring
|
||||
*/
|
||||
public PGPSecretKeyRing removePrivateKeys(KeyFilter filter) {
|
||||
return replacePrivateKeys(GnuPGDummyExtension.NO_PRIVATE_KEY, null, filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all private keys that match the given {@link KeyFilter} from the key ring and replace them with
|
||||
* GNU_DUMMY keys with S2K protection mode {@link GnuPGDummyExtension#DIVERT_TO_CARD}.
|
||||
* This method will set the serial number of the card to 0x00000000000000000000000000000000.
|
||||
*
|
||||
* NOTE: This method does not actually move any keys to a card.
|
||||
*
|
||||
* @param filter filter to select keys for removal
|
||||
* @return modified key ring
|
||||
*/
|
||||
public PGPSecretKeyRing divertPrivateKeysToCard(KeyFilter filter) {
|
||||
return divertPrivateKeysToCard(filter, new byte[16]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all private keys that match the given {@link KeyFilter} from the key ring and replace them with
|
||||
* GNU_DUMMY keys with S2K protection mode {@link GnuPGDummyExtension#DIVERT_TO_CARD}.
|
||||
* This method will include the card serial number into the encoded dummy key.
|
||||
*
|
||||
* NOTE: This method does not actually move any keys to a card.
|
||||
*
|
||||
* @param filter filter to select keys for removal
|
||||
* @param cardSerialNumber serial number of the card (at most 16 bytes long)
|
||||
* @return modified key ring
|
||||
*/
|
||||
public PGPSecretKeyRing divertPrivateKeysToCard(KeyFilter filter, byte[] cardSerialNumber) {
|
||||
if (cardSerialNumber != null && cardSerialNumber.length > 16) {
|
||||
throw new IllegalArgumentException("Card serial number length cannot exceed 16 bytes.");
|
||||
}
|
||||
return replacePrivateKeys(GnuPGDummyExtension.DIVERT_TO_CARD, cardSerialNumber, filter);
|
||||
}
|
||||
|
||||
private PGPSecretKeyRing replacePrivateKeys(GnuPGDummyExtension extension, byte[] serial, KeyFilter filter) {
|
||||
byte[] encodedSerial = serial != null ? encodeSerial(serial) : null;
|
||||
S2K s2k = extensionToS2K(extension);
|
||||
|
||||
List<PGPSecretKey> secretKeyList = new ArrayList<>();
|
||||
for (PGPSecretKey secretKey : keys) {
|
||||
if (!filter.filter(secretKey.getKeyID())) {
|
||||
// No conversion, do not modify subkey
|
||||
secretKeyList.add(secretKey);
|
||||
continue;
|
||||
}
|
||||
|
||||
PublicKeyPacket publicKeyPacket = secretKey.getPublicKey().getPublicKeyPacket();
|
||||
if (secretKey.isMasterKey()) {
|
||||
SecretKeyPacket keyPacket = new SecretKeyPacket(publicKeyPacket,
|
||||
0, SecretKeyPacket.USAGE_SHA1, s2k, null, encodedSerial);
|
||||
PGPSecretKey onCard = new PGPSecretKey(keyPacket, secretKey.getPublicKey());
|
||||
secretKeyList.add(onCard);
|
||||
} else {
|
||||
SecretSubkeyPacket keyPacket = new SecretSubkeyPacket(publicKeyPacket,
|
||||
0, SecretKeyPacket.USAGE_SHA1, s2k, null, encodedSerial);
|
||||
PGPSecretKey onCard = new PGPSecretKey(keyPacket, secretKey.getPublicKey());
|
||||
secretKeyList.add(onCard);
|
||||
}
|
||||
}
|
||||
|
||||
return new PGPSecretKeyRing(secretKeyList);
|
||||
}
|
||||
|
||||
private byte[] encodeSerial(@Nonnull byte[] serial) {
|
||||
byte[] encoded = new byte[serial.length + 1];
|
||||
encoded[0] = (byte) (serial.length & 0xff);
|
||||
System.arraycopy(serial, 0, encoded, 1, serial.length);
|
||||
return encoded;
|
||||
}
|
||||
|
||||
private S2K extensionToS2K(@Nonnull GnuPGDummyExtension extension) {
|
||||
return S2K.gnuDummyS2K(extension == GnuPGDummyExtension.DIVERT_TO_CARD ?
|
||||
S2K.GNUDummyParams.divertToCard() : S2K.GNUDummyParams.noPrivateKey());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter for selecting keys.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface KeyFilter {
|
||||
|
||||
/**
|
||||
* Return true, if the given key should be selected, false otherwise.
|
||||
*
|
||||
* @param keyId id of the key
|
||||
* @return select
|
||||
*/
|
||||
boolean filter(long keyId);
|
||||
|
||||
/**
|
||||
* Select any key.
|
||||
*
|
||||
* @return filter
|
||||
*/
|
||||
static KeyFilter any() {
|
||||
return keyId -> true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Select only the given keyId.
|
||||
*
|
||||
* @param onlyKeyId only acceptable key id
|
||||
* @return filter
|
||||
*/
|
||||
static KeyFilter only(long onlyKeyId) {
|
||||
return keyId -> keyId == onlyKeyId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Select all keyIds which are contained in the given set of ids.
|
||||
*
|
||||
* @param ids set of acceptable keyIds
|
||||
* @return filter
|
||||
*/
|
||||
static KeyFilter selected(Collection<Long> ids) {
|
||||
// noinspection Convert2MethodRef
|
||||
return keyId -> ids.contains(keyId);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/**
|
||||
* Utility classes related to creating keys with GNU DUMMY S2K values.
|
||||
*/
|
||||
package org.gnupg;
|
|
@ -0,0 +1,71 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.algorithm;
|
||||
|
||||
import org.bouncycastle.bcpg.PacketTags;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
public enum OpenPgpPacket {
|
||||
PKESK(PacketTags.PUBLIC_KEY_ENC_SESSION),
|
||||
SIG(PacketTags.SIGNATURE),
|
||||
SKESK(PacketTags.SYMMETRIC_KEY_ENC_SESSION),
|
||||
OPS(PacketTags.ONE_PASS_SIGNATURE),
|
||||
SK(PacketTags.SECRET_KEY),
|
||||
PK(PacketTags.PUBLIC_KEY),
|
||||
SSK(PacketTags.SECRET_SUBKEY),
|
||||
COMP(PacketTags.COMPRESSED_DATA),
|
||||
SED(PacketTags.SYMMETRIC_KEY_ENC),
|
||||
MARKER(PacketTags.MARKER),
|
||||
LIT(PacketTags.LITERAL_DATA),
|
||||
TRUST(PacketTags.TRUST),
|
||||
UID(PacketTags.USER_ID),
|
||||
PSK(PacketTags.PUBLIC_SUBKEY),
|
||||
UATTR(PacketTags.USER_ATTRIBUTE),
|
||||
SEIPD(PacketTags.SYM_ENC_INTEGRITY_PRO),
|
||||
MDC(PacketTags.MOD_DETECTION_CODE),
|
||||
|
||||
EXP_1(PacketTags.EXPERIMENTAL_1),
|
||||
EXP_2(PacketTags.EXPERIMENTAL_2),
|
||||
EXP_3(PacketTags.EXPERIMENTAL_3),
|
||||
EXP_4(PacketTags.EXPERIMENTAL_4),
|
||||
;
|
||||
|
||||
static final Map<Integer, OpenPgpPacket> MAP = new HashMap<>();
|
||||
|
||||
static {
|
||||
for (OpenPgpPacket p : OpenPgpPacket.values()) {
|
||||
MAP.put(p.getTag(), p);
|
||||
}
|
||||
}
|
||||
|
||||
final int tag;
|
||||
|
||||
@Nullable
|
||||
public static OpenPgpPacket fromTag(int tag) {
|
||||
return MAP.get(tag);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public static OpenPgpPacket requireFromTag(int tag) {
|
||||
OpenPgpPacket p = fromTag(tag);
|
||||
if (p == null) {
|
||||
throw new NoSuchElementException("No OpenPGP packet known for tag " + tag);
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
OpenPgpPacket(int tag) {
|
||||
this.tag = tag;
|
||||
}
|
||||
|
||||
int getTag() {
|
||||
return tag;
|
||||
}
|
||||
}
|
|
@ -28,7 +28,7 @@ public enum PublicKeyAlgorithm {
|
|||
/**
|
||||
* RSA with usage encryption.
|
||||
*
|
||||
* @deprecated see https://tools.ietf.org/html/rfc4880#section-13.5
|
||||
* @deprecated see <a href="https://tools.ietf.org/html/rfc4880#section-13.5">Deprecation notice</a>
|
||||
*/
|
||||
@Deprecated
|
||||
RSA_ENCRYPT (PublicKeyAlgorithmTags.RSA_ENCRYPT, false, true),
|
||||
|
@ -36,7 +36,7 @@ public enum PublicKeyAlgorithm {
|
|||
/**
|
||||
* RSA with usage of creating signatures.
|
||||
*
|
||||
* @deprecated see https://tools.ietf.org/html/rfc4880#section-13.5
|
||||
* @deprecated see <a href="https://tools.ietf.org/html/rfc4880#section-13.5">Deprecation notice</a>
|
||||
*/
|
||||
@Deprecated
|
||||
RSA_SIGN (PublicKeyAlgorithmTags.RSA_SIGN, true, false),
|
||||
|
@ -71,7 +71,7 @@ public enum PublicKeyAlgorithm {
|
|||
/**
|
||||
* ElGamal General.
|
||||
*
|
||||
* @deprecated see https://tools.ietf.org/html/rfc4880#section-13.8
|
||||
* @deprecated see <a href="https://tools.ietf.org/html/rfc4880#section-13.8">Deprecation notice</a>
|
||||
*/
|
||||
@Deprecated
|
||||
ELGAMAL_GENERAL (PublicKeyAlgorithmTags.ELGAMAL_GENERAL, true, true),
|
||||
|
|
|
@ -22,8 +22,10 @@ import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
|||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
|
||||
import org.bouncycastle.openpgp.PGPSignature;
|
||||
import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
|
||||
import org.pgpainless.decryption_verification.cleartext_signatures.InMemoryMultiPassStrategy;
|
||||
import org.pgpainless.decryption_verification.cleartext_signatures.MultiPassStrategy;
|
||||
import org.pgpainless.key.SubkeyIdentifier;
|
||||
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
||||
import org.pgpainless.signature.SignatureUtils;
|
||||
import org.pgpainless.util.Passphrase;
|
||||
|
@ -48,6 +50,7 @@ public class ConsumerOptions {
|
|||
|
||||
// Session key for decryption without passphrase/key
|
||||
private SessionKey sessionKey = null;
|
||||
private final Map<SubkeyIdentifier, PublicKeyDataDecryptorFactory> customPublicKeyDataDecryptorFactories = new HashMap<>();
|
||||
|
||||
private final Map<PGPSecretKeyRing, SecretKeyRingProtector> decryptionKeys = new HashMap<>();
|
||||
private final Set<Passphrase> decryptionPassphrases = new HashSet<>();
|
||||
|
@ -238,6 +241,23 @@ public class ConsumerOptions {
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a custom {@link PublicKeyDataDecryptorFactory} which enable decryption of messages, e.g. using
|
||||
* hardware-backed secret keys.
|
||||
* (See e.g. {@link org.pgpainless.decryption_verification.HardwareSecurity.HardwareDataDecryptorFactory}).
|
||||
*
|
||||
* @param factory decryptor factory
|
||||
* @return options
|
||||
*/
|
||||
public ConsumerOptions addCustomDecryptorFactory(@Nonnull CustomPublicKeyDataDecryptorFactory factory) {
|
||||
this.customPublicKeyDataDecryptorFactories.put(factory.getSubkeyIdentifier(), factory);
|
||||
return this;
|
||||
}
|
||||
|
||||
Map<SubkeyIdentifier, PublicKeyDataDecryptorFactory> getCustomDecryptorFactories() {
|
||||
return new HashMap<>(customPublicKeyDataDecryptorFactories);
|
||||
}
|
||||
|
||||
public @Nonnull Set<PGPSecretKeyRing> getDecryptionKeys() {
|
||||
return Collections.unmodifiableSet(decryptionKeys.keySet());
|
||||
}
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.decryption_verification;
|
||||
|
||||
import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
|
||||
import org.pgpainless.key.SubkeyIdentifier;
|
||||
|
||||
public interface CustomPublicKeyDataDecryptorFactory extends PublicKeyDataDecryptorFactory {
|
||||
|
||||
SubkeyIdentifier getSubkeyIdentifier();
|
||||
|
||||
}
|
|
@ -31,7 +31,7 @@ public class DecryptionBuilder implements DecryptionBuilderInterface {
|
|||
throw new IllegalArgumentException("Consumer options cannot be null.");
|
||||
}
|
||||
|
||||
return DecryptionStreamFactory.create(inputStream, consumerOptions);
|
||||
return OpenPgpMessageInputStream.create(inputStream, consumerOptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,65 +1,13 @@
|
|||
// SPDX-FileCopyrightText: 2018 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.decryption_verification;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import org.bouncycastle.util.io.Streams;
|
||||
|
||||
/**
|
||||
* Decryption Stream that handles updating and verification of detached signatures,
|
||||
* as well as verification of integrity-protected input streams once the stream gets closed.
|
||||
*/
|
||||
public class DecryptionStream extends CloseForResultInputStream {
|
||||
|
||||
private final InputStream inputStream;
|
||||
private final IntegrityProtectedInputStream integrityProtectedInputStream;
|
||||
private final InputStream armorStream;
|
||||
|
||||
/**
|
||||
* Create an input stream that handles decryption and - if necessary - integrity protection verification.
|
||||
*
|
||||
* @param wrapped underlying input stream
|
||||
* @param resultBuilder builder for decryption metadata like algorithms, recipients etc.
|
||||
* @param integrityProtectedInputStream in case of data encrypted using SEIP packet close this stream to check integrity
|
||||
* @param armorStream armor stream to verify CRC checksums
|
||||
*/
|
||||
DecryptionStream(@Nonnull InputStream wrapped,
|
||||
@Nonnull OpenPgpMetadata.Builder resultBuilder,
|
||||
IntegrityProtectedInputStream integrityProtectedInputStream,
|
||||
InputStream armorStream) {
|
||||
public abstract class DecryptionStream extends CloseForResultInputStream {
|
||||
public DecryptionStream(@Nonnull OpenPgpMetadata.Builder resultBuilder) {
|
||||
super(resultBuilder);
|
||||
this.inputStream = wrapped;
|
||||
this.integrityProtectedInputStream = integrityProtectedInputStream;
|
||||
this.armorStream = armorStream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if (armorStream != null) {
|
||||
Streams.drain(armorStream);
|
||||
}
|
||||
inputStream.close();
|
||||
if (integrityProtectedInputStream != null) {
|
||||
integrityProtectedInputStream.close();
|
||||
}
|
||||
super.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
int r = inputStream.read();
|
||||
return r;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(@Nonnull byte[] bytes, int offset, int length) throws IOException {
|
||||
int read = inputStream.read(bytes, offset, length);
|
||||
return read;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,650 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2018 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.decryption_verification;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Set;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import org.bouncycastle.bcpg.ArmoredInputStream;
|
||||
import org.bouncycastle.openpgp.PGPCompressedData;
|
||||
import org.bouncycastle.openpgp.PGPEncryptedData;
|
||||
import org.bouncycastle.openpgp.PGPEncryptedDataList;
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPLiteralData;
|
||||
import org.bouncycastle.openpgp.PGPObjectFactory;
|
||||
import org.bouncycastle.openpgp.PGPOnePassSignature;
|
||||
import org.bouncycastle.openpgp.PGPOnePassSignatureList;
|
||||
import org.bouncycastle.openpgp.PGPPBEEncryptedData;
|
||||
import org.bouncycastle.openpgp.PGPPrivateKey;
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKey;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSessionKey;
|
||||
import org.bouncycastle.openpgp.PGPSignature;
|
||||
import org.bouncycastle.openpgp.PGPSignatureList;
|
||||
import org.bouncycastle.openpgp.PGPUtil;
|
||||
import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory;
|
||||
import org.bouncycastle.openpgp.operator.PGPContentVerifierBuilderProvider;
|
||||
import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
|
||||
import org.bouncycastle.openpgp.operator.SessionKeyDataDecryptorFactory;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.algorithm.CompressionAlgorithm;
|
||||
import org.pgpainless.algorithm.EncryptionPurpose;
|
||||
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.exception.FinalIOException;
|
||||
import org.pgpainless.exception.MessageNotIntegrityProtectedException;
|
||||
import org.pgpainless.exception.MissingDecryptionMethodException;
|
||||
import org.pgpainless.exception.MissingLiteralDataException;
|
||||
import org.pgpainless.exception.MissingPassphraseException;
|
||||
import org.pgpainless.exception.SignatureValidationException;
|
||||
import org.pgpainless.exception.UnacceptableAlgorithmException;
|
||||
import org.pgpainless.implementation.ImplementationFactory;
|
||||
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.signature.SignatureUtils;
|
||||
import org.pgpainless.signature.consumer.DetachedSignatureCheck;
|
||||
import org.pgpainless.signature.consumer.OnePassSignatureCheck;
|
||||
import org.pgpainless.util.ArmoredInputStreamFactory;
|
||||
import org.pgpainless.util.Passphrase;
|
||||
import org.pgpainless.util.SessionKey;
|
||||
import org.pgpainless.util.Tuple;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public final class DecryptionStreamFactory {
|
||||
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(DecryptionStreamFactory.class);
|
||||
// Maximum nesting depth of packets (e.g. compression, encryption...)
|
||||
private static final int MAX_PACKET_NESTING_DEPTH = 16;
|
||||
|
||||
private final ConsumerOptions options;
|
||||
private final OpenPgpMetadata.Builder resultBuilder = OpenPgpMetadata.getBuilder();
|
||||
private final List<OnePassSignatureCheck> onePassSignatureChecks = new ArrayList<>();
|
||||
private final List<DetachedSignatureCheck> detachedSignatureChecks = new ArrayList<>();
|
||||
private final Map<Long, OnePassSignatureCheck> onePassSignaturesWithMissingCert = new HashMap<>();
|
||||
|
||||
private static final PGPContentVerifierBuilderProvider verifierBuilderProvider =
|
||||
ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider();
|
||||
private IntegrityProtectedInputStream integrityProtectedEncryptedInputStream;
|
||||
|
||||
|
||||
public static DecryptionStream create(@Nonnull InputStream inputStream,
|
||||
@Nonnull ConsumerOptions options)
|
||||
throws PGPException, IOException {
|
||||
DecryptionStreamFactory factory = new DecryptionStreamFactory(options);
|
||||
OpenPgpInputStream openPgpIn = new OpenPgpInputStream(inputStream);
|
||||
return factory.parseOpenPGPDataAndCreateDecryptionStream(openPgpIn);
|
||||
}
|
||||
|
||||
public DecryptionStreamFactory(ConsumerOptions options) {
|
||||
this.options = options;
|
||||
initializeDetachedSignatures(options.getDetachedSignatures());
|
||||
}
|
||||
|
||||
private void initializeDetachedSignatures(Set<PGPSignature> signatures) {
|
||||
for (PGPSignature signature : signatures) {
|
||||
long issuerKeyId = SignatureUtils.determineIssuerKeyId(signature);
|
||||
PGPPublicKeyRing signingKeyRing = findSignatureVerificationKeyRing(issuerKeyId);
|
||||
if (signingKeyRing == null) {
|
||||
SignatureValidationException ex = new SignatureValidationException(
|
||||
"Missing verification certificate " + Long.toHexString(issuerKeyId));
|
||||
resultBuilder.addInvalidDetachedSignature(new SignatureVerification(signature, null), ex);
|
||||
continue;
|
||||
}
|
||||
PGPPublicKey signingKey = signingKeyRing.getPublicKey(issuerKeyId);
|
||||
SubkeyIdentifier signingKeyIdentifier = new SubkeyIdentifier(signingKeyRing, signingKey.getKeyID());
|
||||
try {
|
||||
signature.init(verifierBuilderProvider, signingKey);
|
||||
DetachedSignatureCheck detachedSignature =
|
||||
new DetachedSignatureCheck(signature, signingKeyRing, signingKeyIdentifier);
|
||||
detachedSignatureChecks.add(detachedSignature);
|
||||
} catch (PGPException e) {
|
||||
SignatureValidationException ex = new SignatureValidationException(
|
||||
"Cannot verify detached signature made by " + signingKeyIdentifier + ".", e);
|
||||
resultBuilder.addInvalidDetachedSignature(new SignatureVerification(signature, signingKeyIdentifier), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private DecryptionStream parseOpenPGPDataAndCreateDecryptionStream(OpenPgpInputStream openPgpIn)
|
||||
throws IOException, PGPException {
|
||||
|
||||
InputStream pgpInStream;
|
||||
InputStream outerDecodingStream;
|
||||
PGPObjectFactory objectFactory;
|
||||
|
||||
// Non-OpenPGP data. We are probably verifying detached signatures
|
||||
if (openPgpIn.isNonOpenPgp() || options.isForceNonOpenPgpData()) {
|
||||
outerDecodingStream = openPgpIn;
|
||||
pgpInStream = wrapInVerifySignatureStream(outerDecodingStream, null);
|
||||
return new DecryptionStream(pgpInStream, resultBuilder, integrityProtectedEncryptedInputStream, null);
|
||||
}
|
||||
|
||||
// Data appears to be OpenPGP message,
|
||||
// or we handle it as such, since user provided a session-key for decryption
|
||||
if (openPgpIn.isLikelyOpenPgpMessage() ||
|
||||
(openPgpIn.isBinaryOpenPgp() && options.getSessionKey() != null)) {
|
||||
outerDecodingStream = openPgpIn;
|
||||
objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory(outerDecodingStream);
|
||||
// Parse OpenPGP message
|
||||
pgpInStream = processPGPPackets(objectFactory, 1);
|
||||
return new DecryptionStream(pgpInStream,
|
||||
resultBuilder, integrityProtectedEncryptedInputStream, null);
|
||||
}
|
||||
|
||||
if (openPgpIn.isAsciiArmored()) {
|
||||
ArmoredInputStream armoredInputStream = ArmoredInputStreamFactory.get(openPgpIn);
|
||||
if (armoredInputStream.isClearText()) {
|
||||
resultBuilder.setCleartextSigned();
|
||||
return parseCleartextSignedMessage(armoredInputStream);
|
||||
} else {
|
||||
outerDecodingStream = armoredInputStream;
|
||||
objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory(outerDecodingStream);
|
||||
// Parse OpenPGP message
|
||||
pgpInStream = processPGPPackets(objectFactory, 1);
|
||||
return new DecryptionStream(pgpInStream,
|
||||
resultBuilder, integrityProtectedEncryptedInputStream,
|
||||
outerDecodingStream);
|
||||
}
|
||||
}
|
||||
|
||||
throw new PGPException("Not sure how to handle the input stream.");
|
||||
}
|
||||
|
||||
private DecryptionStream parseCleartextSignedMessage(ArmoredInputStream armorIn)
|
||||
throws IOException, PGPException {
|
||||
resultBuilder.setCompressionAlgorithm(CompressionAlgorithm.UNCOMPRESSED)
|
||||
.setFileEncoding(StreamEncoding.TEXT);
|
||||
|
||||
MultiPassStrategy multiPassStrategy = options.getMultiPassStrategy();
|
||||
PGPSignatureList signatures = ClearsignedMessageUtil.detachSignaturesFromInbandClearsignedMessage(armorIn, multiPassStrategy.getMessageOutputStream());
|
||||
|
||||
for (PGPSignature signature : signatures) {
|
||||
options.addVerificationOfDetachedSignature(signature);
|
||||
}
|
||||
|
||||
initializeDetachedSignatures(options.getDetachedSignatures());
|
||||
|
||||
InputStream verifyIn = wrapInVerifySignatureStream(multiPassStrategy.getMessageInputStream(), null);
|
||||
return new DecryptionStream(verifyIn, resultBuilder, integrityProtectedEncryptedInputStream,
|
||||
null);
|
||||
}
|
||||
|
||||
private InputStream wrapInVerifySignatureStream(InputStream bufferedIn, @Nullable PGPObjectFactory objectFactory) {
|
||||
return new SignatureInputStream.VerifySignatures(
|
||||
bufferedIn, objectFactory, onePassSignatureChecks,
|
||||
onePassSignaturesWithMissingCert, detachedSignatureChecks, options,
|
||||
resultBuilder);
|
||||
}
|
||||
|
||||
private InputStream processPGPPackets(@Nonnull PGPObjectFactory objectFactory, int depth)
|
||||
throws IOException, PGPException {
|
||||
if (depth >= MAX_PACKET_NESTING_DEPTH) {
|
||||
throw new PGPException("Maximum depth of nested packages exceeded.");
|
||||
}
|
||||
Object nextPgpObject;
|
||||
try {
|
||||
while ((nextPgpObject = objectFactory.nextObject()) != null) {
|
||||
if (nextPgpObject instanceof PGPEncryptedDataList) {
|
||||
return processPGPEncryptedDataList((PGPEncryptedDataList) nextPgpObject, depth);
|
||||
}
|
||||
if (nextPgpObject instanceof PGPCompressedData) {
|
||||
return processPGPCompressedData((PGPCompressedData) nextPgpObject, depth);
|
||||
}
|
||||
if (nextPgpObject instanceof PGPOnePassSignatureList) {
|
||||
return processOnePassSignatureList(objectFactory, (PGPOnePassSignatureList) nextPgpObject, depth);
|
||||
}
|
||||
if (nextPgpObject instanceof PGPLiteralData) {
|
||||
return processPGPLiteralData(objectFactory, (PGPLiteralData) nextPgpObject, depth);
|
||||
}
|
||||
}
|
||||
} catch (FinalIOException e) {
|
||||
throw e;
|
||||
} catch (IOException e) {
|
||||
if (depth == 1 && e.getMessage().contains("invalid armor")) {
|
||||
throw e;
|
||||
}
|
||||
if (depth == 1 && e.getMessage().contains("unknown object in stream:")) {
|
||||
throw new MissingLiteralDataException("No Literal Data Packet found.");
|
||||
} else {
|
||||
throw new FinalIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
throw new MissingLiteralDataException("No Literal Data Packet found");
|
||||
}
|
||||
|
||||
private InputStream processPGPEncryptedDataList(PGPEncryptedDataList pgpEncryptedDataList, int depth)
|
||||
throws PGPException, IOException {
|
||||
LOGGER.debug("Depth {}: Encountered PGPEncryptedDataList", depth);
|
||||
|
||||
SessionKey sessionKey = options.getSessionKey();
|
||||
if (sessionKey != null) {
|
||||
integrityProtectedEncryptedInputStream = decryptWithProvidedSessionKey(pgpEncryptedDataList, sessionKey);
|
||||
InputStream decodedDataStream = PGPUtil.getDecoderStream(integrityProtectedEncryptedInputStream);
|
||||
PGPObjectFactory factory = ImplementationFactory.getInstance().getPGPObjectFactory(decodedDataStream);
|
||||
return processPGPPackets(factory, ++depth);
|
||||
}
|
||||
|
||||
InputStream decryptedDataStream = decryptSessionKey(pgpEncryptedDataList);
|
||||
InputStream decodedDataStream = PGPUtil.getDecoderStream(decryptedDataStream);
|
||||
PGPObjectFactory factory = ImplementationFactory.getInstance().getPGPObjectFactory(decodedDataStream);
|
||||
return processPGPPackets(factory, ++depth);
|
||||
}
|
||||
|
||||
private IntegrityProtectedInputStream decryptWithProvidedSessionKey(
|
||||
PGPEncryptedDataList pgpEncryptedDataList,
|
||||
SessionKey sessionKey)
|
||||
throws PGPException {
|
||||
SessionKeyDataDecryptorFactory decryptorFactory = ImplementationFactory.getInstance()
|
||||
.getSessionKeyDataDecryptorFactory(sessionKey);
|
||||
InputStream decryptedDataStream = null;
|
||||
PGPEncryptedData encryptedData = null;
|
||||
for (PGPEncryptedData pgpEncryptedData : pgpEncryptedDataList) {
|
||||
encryptedData = pgpEncryptedData;
|
||||
if (!options.isIgnoreMDCErrors() && !encryptedData.isIntegrityProtected()) {
|
||||
throw new MessageNotIntegrityProtectedException();
|
||||
}
|
||||
|
||||
if (encryptedData instanceof PGPPBEEncryptedData) {
|
||||
PGPPBEEncryptedData pbeEncrypted = (PGPPBEEncryptedData) encryptedData;
|
||||
decryptedDataStream = pbeEncrypted.getDataStream(decryptorFactory);
|
||||
break;
|
||||
} else if (encryptedData instanceof PGPPublicKeyEncryptedData) {
|
||||
PGPPublicKeyEncryptedData pkEncrypted = (PGPPublicKeyEncryptedData) encryptedData;
|
||||
decryptedDataStream = pkEncrypted.getDataStream(decryptorFactory);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (decryptedDataStream == null) {
|
||||
throw new PGPException("No valid PGP data encountered.");
|
||||
}
|
||||
|
||||
resultBuilder.setSessionKey(sessionKey);
|
||||
throwIfAlgorithmIsRejected(sessionKey.getAlgorithm());
|
||||
integrityProtectedEncryptedInputStream =
|
||||
new IntegrityProtectedInputStream(decryptedDataStream, encryptedData, options);
|
||||
return integrityProtectedEncryptedInputStream;
|
||||
}
|
||||
|
||||
private InputStream processPGPCompressedData(PGPCompressedData pgpCompressedData, int depth)
|
||||
throws PGPException, IOException {
|
||||
try {
|
||||
CompressionAlgorithm compressionAlgorithm = CompressionAlgorithm.requireFromId(pgpCompressedData.getAlgorithm());
|
||||
LOGGER.debug("Depth {}: Encountered PGPCompressedData: {}", depth, compressionAlgorithm);
|
||||
resultBuilder.setCompressionAlgorithm(compressionAlgorithm);
|
||||
} catch (NoSuchElementException e) {
|
||||
throw new PGPException("Unknown compression algorithm encountered.", e);
|
||||
}
|
||||
|
||||
InputStream inflatedDataStream = pgpCompressedData.getDataStream();
|
||||
InputStream decodedDataStream = PGPUtil.getDecoderStream(inflatedDataStream);
|
||||
PGPObjectFactory objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory(decodedDataStream);
|
||||
|
||||
return processPGPPackets(objectFactory, ++depth);
|
||||
}
|
||||
|
||||
private InputStream processOnePassSignatureList(
|
||||
@Nonnull PGPObjectFactory objectFactory,
|
||||
PGPOnePassSignatureList onePassSignatures,
|
||||
int depth)
|
||||
throws PGPException, IOException {
|
||||
LOGGER.debug("Depth {}: Encountered PGPOnePassSignatureList of size {}", depth, onePassSignatures.size());
|
||||
initOnePassSignatures(onePassSignatures);
|
||||
return processPGPPackets(objectFactory, depth);
|
||||
}
|
||||
|
||||
private InputStream processPGPLiteralData(
|
||||
@Nonnull PGPObjectFactory objectFactory,
|
||||
PGPLiteralData pgpLiteralData,
|
||||
int depth) {
|
||||
LOGGER.debug("Depth {}: Found PGPLiteralData", depth);
|
||||
InputStream literalDataInputStream = pgpLiteralData.getInputStream();
|
||||
|
||||
resultBuilder.setFileName(pgpLiteralData.getFileName())
|
||||
.setModificationDate(pgpLiteralData.getModificationTime())
|
||||
.setFileEncoding(StreamEncoding.requireFromCode(pgpLiteralData.getFormat()));
|
||||
|
||||
if (onePassSignatureChecks.isEmpty() && onePassSignaturesWithMissingCert.isEmpty()) {
|
||||
LOGGER.debug("No OnePassSignatures found -> We are done");
|
||||
return literalDataInputStream;
|
||||
}
|
||||
|
||||
return new SignatureInputStream.VerifySignatures(literalDataInputStream, objectFactory,
|
||||
onePassSignatureChecks, onePassSignaturesWithMissingCert, detachedSignatureChecks, options, resultBuilder) {
|
||||
};
|
||||
}
|
||||
|
||||
private InputStream decryptSessionKey(@Nonnull PGPEncryptedDataList encryptedDataList)
|
||||
throws PGPException {
|
||||
Iterator<PGPEncryptedData> encryptedDataIterator = encryptedDataList.getEncryptedDataObjects();
|
||||
if (!encryptedDataIterator.hasNext()) {
|
||||
throw new PGPException("Decryption failed - EncryptedDataList has no items");
|
||||
}
|
||||
|
||||
PGPPrivateKey decryptionKey = null;
|
||||
PGPPublicKeyEncryptedData encryptedSessionKey = null;
|
||||
|
||||
List<PGPPBEEncryptedData> passphraseProtected = new ArrayList<>();
|
||||
List<PGPPublicKeyEncryptedData> publicKeyProtected = new ArrayList<>();
|
||||
List<Tuple<SubkeyIdentifier, PGPPublicKeyEncryptedData>> postponedDueToMissingPassphrase = new ArrayList<>();
|
||||
|
||||
// Sort PKESK and SKESK packets
|
||||
while (encryptedDataIterator.hasNext()) {
|
||||
PGPEncryptedData encryptedData = encryptedDataIterator.next();
|
||||
|
||||
if (!encryptedData.isIntegrityProtected() && !options.isIgnoreMDCErrors()) {
|
||||
throw new MessageNotIntegrityProtectedException();
|
||||
}
|
||||
|
||||
// SKESK
|
||||
if (encryptedData instanceof PGPPBEEncryptedData) {
|
||||
passphraseProtected.add((PGPPBEEncryptedData) encryptedData);
|
||||
}
|
||||
// PKESK
|
||||
else if (encryptedData instanceof PGPPublicKeyEncryptedData) {
|
||||
publicKeyProtected.add((PGPPublicKeyEncryptedData) encryptedData);
|
||||
}
|
||||
}
|
||||
|
||||
// Try decryption with passphrases first
|
||||
for (PGPPBEEncryptedData pbeEncryptedData : passphraseProtected) {
|
||||
for (Passphrase passphrase : options.getDecryptionPassphrases()) {
|
||||
PBEDataDecryptorFactory passphraseDecryptor = ImplementationFactory.getInstance()
|
||||
.getPBEDataDecryptorFactory(passphrase);
|
||||
try {
|
||||
InputStream decryptedDataStream = pbeEncryptedData.getDataStream(passphraseDecryptor);
|
||||
|
||||
PGPSessionKey pgpSessionKey = pbeEncryptedData.getSessionKey(passphraseDecryptor);
|
||||
SessionKey sessionKey = new SessionKey(pgpSessionKey);
|
||||
resultBuilder.setSessionKey(sessionKey);
|
||||
|
||||
throwIfAlgorithmIsRejected(sessionKey.getAlgorithm());
|
||||
|
||||
integrityProtectedEncryptedInputStream =
|
||||
new IntegrityProtectedInputStream(decryptedDataStream, pbeEncryptedData, options);
|
||||
|
||||
return integrityProtectedEncryptedInputStream;
|
||||
} catch (PGPException e) {
|
||||
LOGGER.debug("Probable passphrase mismatch, skip PBE encrypted data block", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Then try decryption with public key encryption
|
||||
for (PGPPublicKeyEncryptedData publicKeyEncryptedData : publicKeyProtected) {
|
||||
PGPPrivateKey privateKey = null;
|
||||
if (options.getDecryptionKeys().isEmpty()) {
|
||||
break;
|
||||
}
|
||||
|
||||
long keyId = publicKeyEncryptedData.getKeyID();
|
||||
// Wildcard KeyID
|
||||
if (keyId == 0L) {
|
||||
LOGGER.debug("Hidden recipient detected. Try to decrypt with all available secret keys.");
|
||||
for (PGPSecretKeyRing secretKeys : options.getDecryptionKeys()) {
|
||||
if (privateKey != null) {
|
||||
break;
|
||||
}
|
||||
KeyRingInfo info = new KeyRingInfo(secretKeys);
|
||||
List<PGPPublicKey> encryptionSubkeys = info.getEncryptionSubkeys(EncryptionPurpose.ANY);
|
||||
for (PGPPublicKey pubkey : encryptionSubkeys) {
|
||||
PGPSecretKey secretKey = secretKeys.getSecretKey(pubkey.getKeyID());
|
||||
// Skip missing secret key
|
||||
if (secretKey == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
privateKey = tryPublicKeyDecryption(secretKeys, secretKey, publicKeyEncryptedData,
|
||||
postponedDueToMissingPassphrase, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Non-wildcard key-id
|
||||
else {
|
||||
LOGGER.debug("PGPEncryptedData is encrypted for key {}", Long.toHexString(keyId));
|
||||
resultBuilder.addRecipientKeyId(keyId);
|
||||
|
||||
PGPSecretKeyRing secretKeys = findDecryptionKeyRing(keyId);
|
||||
if (secretKeys == null) {
|
||||
LOGGER.debug("Missing certificate of {}. Skip.", Long.toHexString(keyId));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Make sure that the recipient key is encryption capable and non-expired
|
||||
KeyRingInfo info = new KeyRingInfo(secretKeys);
|
||||
List<PGPPublicKey> encryptionSubkeys = info.getEncryptionSubkeys(EncryptionPurpose.ANY);
|
||||
|
||||
PGPSecretKey secretKey = null;
|
||||
for (PGPPublicKey pubkey : encryptionSubkeys) {
|
||||
if (pubkey.getKeyID() == keyId) {
|
||||
secretKey = secretKeys.getSecretKey(keyId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (secretKey == null) {
|
||||
LOGGER.debug("Key " + Long.toHexString(keyId) + " is not valid or not capable for decryption.");
|
||||
} else {
|
||||
privateKey = tryPublicKeyDecryption(secretKeys, secretKey, publicKeyEncryptedData,
|
||||
postponedDueToMissingPassphrase, true);
|
||||
}
|
||||
}
|
||||
if (privateKey == null) {
|
||||
continue;
|
||||
}
|
||||
decryptionKey = privateKey;
|
||||
encryptedSessionKey = publicKeyEncryptedData;
|
||||
break;
|
||||
}
|
||||
|
||||
// Try postponed keys with missing passphrases (will cause missing passphrase callbacks to fire)
|
||||
if (encryptedSessionKey == null) {
|
||||
|
||||
if (options.getMissingKeyPassphraseStrategy() == MissingKeyPassphraseStrategy.THROW_EXCEPTION) {
|
||||
// Non-interactive mode: Throw an exception with all locked decryption keys
|
||||
Set<SubkeyIdentifier> keyIds = new HashSet<>();
|
||||
for (Tuple<SubkeyIdentifier, ?> k : postponedDueToMissingPassphrase) {
|
||||
keyIds.add(k.getA());
|
||||
}
|
||||
if (!keyIds.isEmpty()) {
|
||||
throw new MissingPassphraseException(keyIds);
|
||||
}
|
||||
}
|
||||
else if (options.getMissingKeyPassphraseStrategy() == MissingKeyPassphraseStrategy.INTERACTIVE) {
|
||||
// Interactive mode: Fire protector callbacks to get passphrases interactively
|
||||
for (Tuple<SubkeyIdentifier, PGPPublicKeyEncryptedData> missingPassphrases : postponedDueToMissingPassphrase) {
|
||||
SubkeyIdentifier keyId = missingPassphrases.getA();
|
||||
PGPPublicKeyEncryptedData publicKeyEncryptedData = missingPassphrases.getB();
|
||||
PGPSecretKeyRing secretKeys = findDecryptionKeyRing(keyId.getKeyId());
|
||||
PGPSecretKey secretKey = secretKeys.getSecretKey(keyId.getSubkeyId());
|
||||
|
||||
PGPPrivateKey privateKey = tryPublicKeyDecryption(secretKeys, secretKey, publicKeyEncryptedData,
|
||||
postponedDueToMissingPassphrase, false);
|
||||
if (privateKey == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
decryptionKey = privateKey;
|
||||
encryptedSessionKey = publicKeyEncryptedData;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
throw new IllegalStateException("Invalid PostponedKeysStrategy set in consumer options.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return decryptWith(encryptedSessionKey, decryptionKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try decryption of the provided public-key-encrypted-data using the given secret key.
|
||||
* If the secret key is encrypted and the secret key protector does not have a passphrase available and the boolean
|
||||
* postponeIfMissingPassphrase is true, data decryption is postponed by pushing a tuple of the encrypted data decryption key
|
||||
* identifier to the postponed list.
|
||||
*
|
||||
* This method only returns a non-null private key, if the private key is able to decrypt the message successfully.
|
||||
*
|
||||
* @param secretKeys secret key ring
|
||||
* @param secretKey secret key
|
||||
* @param publicKeyEncryptedData encrypted data which is tried to decrypt using the secret key
|
||||
* @param postponed list of postponed decryptions due to missing secret key passphrases
|
||||
* @param postponeIfMissingPassphrase flag to specify whether missing secret key passphrases should result in postponed decryption
|
||||
* @return private key if decryption is successful, null if decryption is unsuccessful or postponed
|
||||
*
|
||||
* @throws PGPException in case of an OpenPGP error
|
||||
*/
|
||||
private PGPPrivateKey tryPublicKeyDecryption(
|
||||
PGPSecretKeyRing secretKeys,
|
||||
PGPSecretKey secretKey,
|
||||
PGPPublicKeyEncryptedData publicKeyEncryptedData,
|
||||
List<Tuple<SubkeyIdentifier, PGPPublicKeyEncryptedData>> postponed,
|
||||
boolean postponeIfMissingPassphrase) throws PGPException {
|
||||
SecretKeyRingProtector protector = options.getSecretKeyProtector(secretKeys);
|
||||
|
||||
if (postponeIfMissingPassphrase && !protector.hasPassphraseFor(secretKey.getKeyID())) {
|
||||
// Postpone decryption with key with missing passphrase
|
||||
SubkeyIdentifier identifier = new SubkeyIdentifier(secretKeys, secretKey.getKeyID());
|
||||
postponed.add(new Tuple<>(identifier, publicKeyEncryptedData));
|
||||
return null;
|
||||
}
|
||||
|
||||
PGPPrivateKey privateKey = UnlockSecretKey.unlockSecretKey(
|
||||
secretKey, protector.getDecryptor(secretKey.getKeyID()));
|
||||
|
||||
// test if we have the right private key
|
||||
PublicKeyDataDecryptorFactory decryptorFactory = ImplementationFactory.getInstance()
|
||||
.getPublicKeyDataDecryptorFactory(privateKey);
|
||||
try {
|
||||
publicKeyEncryptedData.getSymmetricAlgorithm(decryptorFactory); // will only succeed if we have the right secret key
|
||||
LOGGER.debug("Found correct decryption key {}.", Long.toHexString(secretKey.getKeyID()));
|
||||
resultBuilder.setDecryptionKey(new SubkeyIdentifier(secretKeys, privateKey.getKeyID()));
|
||||
return privateKey;
|
||||
} catch (PGPException | ClassCastException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private InputStream decryptWith(PGPPublicKeyEncryptedData encryptedSessionKey, PGPPrivateKey decryptionKey)
|
||||
throws PGPException {
|
||||
if (decryptionKey == null || encryptedSessionKey == null) {
|
||||
throw new MissingDecryptionMethodException("Decryption failed - No suitable decryption key or passphrase found");
|
||||
}
|
||||
|
||||
PublicKeyDataDecryptorFactory dataDecryptor = ImplementationFactory.getInstance()
|
||||
.getPublicKeyDataDecryptorFactory(decryptionKey);
|
||||
|
||||
PGPSessionKey pgpSessionKey = encryptedSessionKey.getSessionKey(dataDecryptor);
|
||||
SessionKey sessionKey = new SessionKey(pgpSessionKey);
|
||||
resultBuilder.setSessionKey(sessionKey);
|
||||
|
||||
SymmetricKeyAlgorithm symmetricKeyAlgorithm = sessionKey.getAlgorithm();
|
||||
if (symmetricKeyAlgorithm == SymmetricKeyAlgorithm.NULL) {
|
||||
LOGGER.debug("Message is unencrypted");
|
||||
} else {
|
||||
LOGGER.debug("Message is encrypted using {}", symmetricKeyAlgorithm);
|
||||
}
|
||||
throwIfAlgorithmIsRejected(symmetricKeyAlgorithm);
|
||||
|
||||
integrityProtectedEncryptedInputStream = new IntegrityProtectedInputStream(
|
||||
encryptedSessionKey.getDataStream(dataDecryptor), encryptedSessionKey, options);
|
||||
return integrityProtectedEncryptedInputStream;
|
||||
}
|
||||
|
||||
private void throwIfAlgorithmIsRejected(SymmetricKeyAlgorithm algorithm)
|
||||
throws UnacceptableAlgorithmException {
|
||||
if (!PGPainless.getPolicy().getSymmetricKeyDecryptionAlgorithmPolicy().isAcceptable(algorithm)) {
|
||||
throw new UnacceptableAlgorithmException("Data is "
|
||||
+ (algorithm == SymmetricKeyAlgorithm.NULL ?
|
||||
"unencrypted" :
|
||||
"encrypted with symmetric algorithm " + algorithm) + " which is not acceptable as per PGPainless' policy.\n" +
|
||||
"To mark this algorithm as acceptable, use PGPainless.getPolicy().setSymmetricKeyDecryptionAlgorithmPolicy().");
|
||||
}
|
||||
}
|
||||
|
||||
private void initOnePassSignatures(@Nonnull PGPOnePassSignatureList onePassSignatureList)
|
||||
throws PGPException {
|
||||
Iterator<PGPOnePassSignature> iterator = onePassSignatureList.iterator();
|
||||
if (!iterator.hasNext()) {
|
||||
throw new PGPException("Verification failed - No OnePassSignatures found");
|
||||
}
|
||||
|
||||
processOnePassSignatures(iterator);
|
||||
}
|
||||
|
||||
private void processOnePassSignatures(Iterator<PGPOnePassSignature> signatures)
|
||||
throws PGPException {
|
||||
while (signatures.hasNext()) {
|
||||
PGPOnePassSignature signature = signatures.next();
|
||||
processOnePassSignature(signature);
|
||||
}
|
||||
}
|
||||
|
||||
private void processOnePassSignature(PGPOnePassSignature signature)
|
||||
throws PGPException {
|
||||
final long keyId = signature.getKeyID();
|
||||
|
||||
LOGGER.debug("Encountered OnePassSignature from {}", Long.toHexString(keyId));
|
||||
|
||||
// Find public key
|
||||
PGPPublicKeyRing verificationKeyRing = findSignatureVerificationKeyRing(keyId);
|
||||
if (verificationKeyRing == null) {
|
||||
onePassSignaturesWithMissingCert.put(keyId, new OnePassSignatureCheck(signature, null));
|
||||
return;
|
||||
}
|
||||
PGPPublicKey verificationKey = verificationKeyRing.getPublicKey(keyId);
|
||||
|
||||
signature.init(verifierBuilderProvider, verificationKey);
|
||||
OnePassSignatureCheck onePassSignature = new OnePassSignatureCheck(signature, verificationKeyRing);
|
||||
onePassSignatureChecks.add(onePassSignature);
|
||||
}
|
||||
|
||||
private PGPSecretKeyRing findDecryptionKeyRing(long keyId) {
|
||||
for (PGPSecretKeyRing key : options.getDecryptionKeys()) {
|
||||
if (key.getSecretKey(keyId) != null) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private PGPPublicKeyRing findSignatureVerificationKeyRing(long keyId) {
|
||||
PGPPublicKeyRing verificationKeyRing = null;
|
||||
for (PGPPublicKeyRing publicKeyRing : options.getCertificates()) {
|
||||
PGPPublicKey verificationKey = publicKeyRing.getPublicKey(keyId);
|
||||
if (verificationKey != null) {
|
||||
LOGGER.debug("Found public key {} for signature verification", Long.toHexString(keyId));
|
||||
verificationKeyRing = publicKeyRing;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (verificationKeyRing == null && options.getMissingCertificateCallback() != null) {
|
||||
verificationKeyRing = options.getMissingCertificateCallback().onMissingPublicKeyEncountered(keyId);
|
||||
}
|
||||
|
||||
return verificationKeyRing;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.decryption_verification;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.operator.PGPDataDecryptor;
|
||||
import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
|
||||
import org.bouncycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory;
|
||||
import org.pgpainless.key.SubkeyIdentifier;
|
||||
|
||||
/**
|
||||
* Enable integration of hardware-backed OpenPGP keys.
|
||||
*/
|
||||
public class HardwareSecurity {
|
||||
|
||||
public interface DecryptionCallback {
|
||||
|
||||
/**
|
||||
* Delegate decryption of a Public-Key-Encrypted-Session-Key (PKESK) to an external API for dealing with
|
||||
* hardware security modules such as smartcards or TPMs.
|
||||
*
|
||||
* If decryption fails for some reason, a subclass of the {@link HardwareSecurityException} is thrown.
|
||||
*
|
||||
* @param keyId id of the key
|
||||
* @param keyAlgorithm algorithm
|
||||
* @param sessionKeyData encrypted session key
|
||||
*
|
||||
* @return decrypted session key
|
||||
* @throws HardwareSecurityException exception
|
||||
*/
|
||||
byte[] decryptSessionKey(long keyId, int keyAlgorithm, byte[] sessionKeyData)
|
||||
throws HardwareSecurityException;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of {@link PublicKeyDataDecryptorFactory} which delegates decryption of encrypted session keys
|
||||
* to a {@link DecryptionCallback}.
|
||||
* Users can provide such a callback to delegate decryption of messages to hardware security SDKs.
|
||||
*/
|
||||
public static class HardwareDataDecryptorFactory implements CustomPublicKeyDataDecryptorFactory {
|
||||
|
||||
private final DecryptionCallback callback;
|
||||
// luckily we can instantiate the BcPublicKeyDataDecryptorFactory with null as argument.
|
||||
private final PublicKeyDataDecryptorFactory factory =
|
||||
new BcPublicKeyDataDecryptorFactory(null);
|
||||
private final SubkeyIdentifier subkey;
|
||||
|
||||
/**
|
||||
* Create a new {@link HardwareDataDecryptorFactory}.
|
||||
*
|
||||
* @param callback decryption callback
|
||||
*/
|
||||
public HardwareDataDecryptorFactory(SubkeyIdentifier subkeyIdentifier, DecryptionCallback callback) {
|
||||
this.callback = callback;
|
||||
this.subkey = subkeyIdentifier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] recoverSessionData(int keyAlgorithm, byte[][] secKeyData)
|
||||
throws PGPException {
|
||||
try {
|
||||
// delegate decryption to the callback
|
||||
return callback.decryptSessionKey(subkey.getSubkeyId(), keyAlgorithm, secKeyData[0]);
|
||||
} catch (HardwareSecurityException e) {
|
||||
throw new PGPException("Hardware-backed decryption failed.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int encAlgorithm, byte[] key)
|
||||
throws PGPException {
|
||||
return factory.createDataDecryptor(withIntegrityPacket, encAlgorithm, key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PGPDataDecryptor createDataDecryptor(int aeadAlgorithm, byte[] iv, int chunkSize, int encAlgorithm, byte[] key)
|
||||
throws PGPException {
|
||||
return factory.createDataDecryptor(aeadAlgorithm, iv, chunkSize, encAlgorithm, key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SubkeyIdentifier getSubkeyIdentifier() {
|
||||
return subkey;
|
||||
}
|
||||
}
|
||||
|
||||
public static class HardwareSecurityException
|
||||
extends Exception {
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -11,12 +11,17 @@ import javax.annotation.Nonnull;
|
|||
import org.bouncycastle.openpgp.PGPEncryptedData;
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.pgpainless.exception.ModificationDetectionException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class IntegrityProtectedInputStream extends InputStream {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(IntegrityProtectedInputStream.class);
|
||||
|
||||
private final InputStream inputStream;
|
||||
private final PGPEncryptedData encryptedData;
|
||||
private final ConsumerOptions options;
|
||||
private boolean closed = false;
|
||||
|
||||
public IntegrityProtectedInputStream(InputStream inputStream, PGPEncryptedData encryptedData, ConsumerOptions options) {
|
||||
this.inputStream = inputStream;
|
||||
|
@ -36,11 +41,17 @@ public class IntegrityProtectedInputStream extends InputStream {
|
|||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if (closed) {
|
||||
return;
|
||||
}
|
||||
closed = true;
|
||||
|
||||
if (encryptedData.isIntegrityProtected() && !options.isIgnoreMDCErrors()) {
|
||||
try {
|
||||
if (!encryptedData.verify()) {
|
||||
throw new ModificationDetectionException();
|
||||
}
|
||||
LOGGER.debug("Integrity Protection check passed");
|
||||
} catch (PGPException e) {
|
||||
throw new IOException("Failed to verify integrity protection", e);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,456 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.decryption_verification;
|
||||
|
||||
import org.pgpainless.algorithm.CompressionAlgorithm;
|
||||
import org.pgpainless.algorithm.StreamEncoding;
|
||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
||||
import org.pgpainless.exception.MalformedOpenPgpMessageException;
|
||||
import org.pgpainless.key.SubkeyIdentifier;
|
||||
import org.pgpainless.util.SessionKey;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
public class MessageMetadata {
|
||||
|
||||
protected Message message;
|
||||
|
||||
public MessageMetadata(@Nonnull Message message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public @Nullable SymmetricKeyAlgorithm getEncryptionAlgorithm() {
|
||||
Iterator<SymmetricKeyAlgorithm> algorithms = getEncryptionAlgorithms();
|
||||
if (algorithms.hasNext()) {
|
||||
return algorithms.next();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public @Nonnull Iterator<SymmetricKeyAlgorithm> getEncryptionAlgorithms() {
|
||||
return new LayerIterator<SymmetricKeyAlgorithm>(message) {
|
||||
@Override
|
||||
public boolean matches(Nested layer) {
|
||||
return layer instanceof EncryptedData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SymmetricKeyAlgorithm getProperty(Layer last) {
|
||||
return ((EncryptedData) last).algorithm;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public @Nullable CompressionAlgorithm getCompressionAlgorithm() {
|
||||
Iterator<CompressionAlgorithm> algorithms = getCompressionAlgorithms();
|
||||
if (algorithms.hasNext()) {
|
||||
return algorithms.next();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public @Nonnull Iterator<CompressionAlgorithm> getCompressionAlgorithms() {
|
||||
return new LayerIterator<CompressionAlgorithm>(message) {
|
||||
@Override
|
||||
public boolean matches(Nested layer) {
|
||||
return layer instanceof CompressedData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompressionAlgorithm getProperty(Layer last) {
|
||||
return ((CompressedData) last).algorithm;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public @Nullable SessionKey getSessionKey() {
|
||||
Iterator<SessionKey> sessionKeys = getSessionKeys();
|
||||
if (sessionKeys.hasNext()) {
|
||||
return sessionKeys.next();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public @Nonnull Iterator<SessionKey> getSessionKeys() {
|
||||
return new LayerIterator<SessionKey>(message) {
|
||||
@Override
|
||||
boolean matches(Nested layer) {
|
||||
return layer instanceof EncryptedData;
|
||||
}
|
||||
|
||||
@Override
|
||||
SessionKey getProperty(Layer last) {
|
||||
return ((EncryptedData) last).getSessionKey();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public List<SignatureVerification> getVerifiedDetachedSignatures() {
|
||||
return new ArrayList<>(message.verifiedDetachedSignatures);
|
||||
}
|
||||
|
||||
public List<SignatureVerification.Failure> getRejectedDetachedSignatures() {
|
||||
return new ArrayList<>(message.rejectedDetachedSignatures);
|
||||
}
|
||||
|
||||
public @Nonnull List<SignatureVerification> getVerifiedInlineSignatures() {
|
||||
List<SignatureVerification> verifications = new ArrayList<>();
|
||||
Iterator<List<SignatureVerification>> verificationsByLayer = getVerifiedInlineSignaturesByLayer();
|
||||
while (verificationsByLayer.hasNext()) {
|
||||
verifications.addAll(verificationsByLayer.next());
|
||||
}
|
||||
return verifications;
|
||||
}
|
||||
|
||||
public @Nonnull Iterator<List<SignatureVerification>> getVerifiedInlineSignaturesByLayer() {
|
||||
return new LayerIterator<List<SignatureVerification>>(message) {
|
||||
@Override
|
||||
boolean matches(Nested layer) {
|
||||
return layer instanceof Layer;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean matches(Layer layer) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
List<SignatureVerification> getProperty(Layer last) {
|
||||
List<SignatureVerification> list = new ArrayList<>();
|
||||
list.addAll(last.getVerifiedOnePassSignatures());
|
||||
list.addAll(last.getVerifiedPrependedSignatures());
|
||||
return list;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public @Nonnull List<SignatureVerification.Failure> getRejectedInlineSignatures() {
|
||||
List<SignatureVerification.Failure> rejected = new ArrayList<>();
|
||||
Iterator<List<SignatureVerification.Failure>> rejectedByLayer = getRejectedInlineSignaturesByLayer();
|
||||
while (rejectedByLayer.hasNext()) {
|
||||
rejected.addAll(rejectedByLayer.next());
|
||||
}
|
||||
return rejected;
|
||||
}
|
||||
|
||||
public @Nonnull Iterator<List<SignatureVerification.Failure>> getRejectedInlineSignaturesByLayer() {
|
||||
return new LayerIterator<List<SignatureVerification.Failure>>(message) {
|
||||
@Override
|
||||
boolean matches(Nested layer) {
|
||||
return layer instanceof Layer;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean matches(Layer layer) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
List<SignatureVerification.Failure> getProperty(Layer last) {
|
||||
List<SignatureVerification.Failure> list = new ArrayList<>();
|
||||
list.addAll(last.getRejectedOnePassSignatures());
|
||||
list.addAll(last.getRejectedPrependedSignatures());
|
||||
return list;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public String getFilename() {
|
||||
LiteralData literalData = findLiteralData();
|
||||
if (literalData == null) {
|
||||
return null;
|
||||
}
|
||||
return literalData.getFileName();
|
||||
}
|
||||
|
||||
public Date getModificationDate() {
|
||||
LiteralData literalData = findLiteralData();
|
||||
if (literalData == null) {
|
||||
return null;
|
||||
}
|
||||
return literalData.getModificationDate();
|
||||
}
|
||||
|
||||
public StreamEncoding getFormat() {
|
||||
LiteralData literalData = findLiteralData();
|
||||
if (literalData == null) {
|
||||
return null;
|
||||
}
|
||||
return literalData.getFormat();
|
||||
}
|
||||
|
||||
private LiteralData findLiteralData() {
|
||||
Nested nested = message.child;
|
||||
if (nested == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
while (nested.hasNestedChild()) {
|
||||
Layer layer = (Layer) nested;
|
||||
nested = layer.child;
|
||||
}
|
||||
return (LiteralData) nested;
|
||||
}
|
||||
|
||||
public SubkeyIdentifier getDecryptionKey() {
|
||||
Iterator<SubkeyIdentifier> iterator = new LayerIterator<SubkeyIdentifier>(message) {
|
||||
@Override
|
||||
public boolean matches(Nested layer) {
|
||||
return layer instanceof EncryptedData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SubkeyIdentifier getProperty(Layer last) {
|
||||
return ((EncryptedData) last).decryptionKey;
|
||||
}
|
||||
};
|
||||
if (iterator.hasNext()) {
|
||||
return iterator.next();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public abstract static class Layer {
|
||||
public static final int MAX_LAYER_DEPTH = 16;
|
||||
protected final int depth;
|
||||
protected final List<SignatureVerification> verifiedDetachedSignatures = new ArrayList<>();
|
||||
protected final List<SignatureVerification.Failure> rejectedDetachedSignatures = new ArrayList<>();
|
||||
protected final List<SignatureVerification> verifiedOnePassSignatures = new ArrayList<>();
|
||||
protected final List<SignatureVerification.Failure> rejectedOnePassSignatures = new ArrayList<>();
|
||||
protected final List<SignatureVerification> verifiedPrependedSignatures = new ArrayList<>();
|
||||
protected final List<SignatureVerification.Failure> rejectedPrependedSignatures = new ArrayList<>();
|
||||
protected Nested child;
|
||||
|
||||
public Layer(int depth) {
|
||||
this.depth = depth;
|
||||
if (depth > MAX_LAYER_DEPTH) {
|
||||
throw new MalformedOpenPgpMessageException("Maximum nesting depth exceeded.");
|
||||
}
|
||||
}
|
||||
|
||||
public Nested getChild() {
|
||||
return child;
|
||||
}
|
||||
|
||||
public void setChild(Nested child) {
|
||||
this.child = child;
|
||||
}
|
||||
|
||||
public List<SignatureVerification> getVerifiedDetachedSignatures() {
|
||||
return new ArrayList<>(verifiedDetachedSignatures);
|
||||
}
|
||||
|
||||
public List<SignatureVerification.Failure> getRejectedDetachedSignatures() {
|
||||
return new ArrayList<>(rejectedDetachedSignatures);
|
||||
}
|
||||
|
||||
void addVerifiedDetachedSignature(SignatureVerification signatureVerification) {
|
||||
verifiedDetachedSignatures.add(signatureVerification);
|
||||
}
|
||||
|
||||
void addRejectedDetachedSignature(SignatureVerification.Failure failure) {
|
||||
rejectedDetachedSignatures.add(failure);
|
||||
}
|
||||
|
||||
public List<SignatureVerification> getVerifiedOnePassSignatures() {
|
||||
return new ArrayList<>(verifiedOnePassSignatures);
|
||||
}
|
||||
|
||||
public List<SignatureVerification.Failure> getRejectedOnePassSignatures() {
|
||||
return new ArrayList<>(rejectedOnePassSignatures);
|
||||
}
|
||||
|
||||
void addVerifiedOnePassSignature(SignatureVerification verifiedOnePassSignature) {
|
||||
this.verifiedOnePassSignatures.add(verifiedOnePassSignature);
|
||||
}
|
||||
|
||||
void addRejectedOnePassSignature(SignatureVerification.Failure rejected) {
|
||||
this.rejectedOnePassSignatures.add(rejected);
|
||||
}
|
||||
|
||||
public List<SignatureVerification> getVerifiedPrependedSignatures() {
|
||||
return new ArrayList<>(verifiedPrependedSignatures);
|
||||
}
|
||||
|
||||
public List<SignatureVerification.Failure> getRejectedPrependedSignatures() {
|
||||
return new ArrayList<>(rejectedPrependedSignatures);
|
||||
}
|
||||
|
||||
void addVerifiedPrependedSignature(SignatureVerification verified) {
|
||||
this.verifiedPrependedSignatures.add(verified);
|
||||
}
|
||||
|
||||
void addRejectedPrependedSignature(SignatureVerification.Failure rejected) {
|
||||
this.rejectedPrependedSignatures.add(rejected);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public interface Nested {
|
||||
boolean hasNestedChild();
|
||||
}
|
||||
|
||||
public static class Message extends Layer {
|
||||
|
||||
public Message() {
|
||||
super(0);
|
||||
}
|
||||
}
|
||||
|
||||
public static class LiteralData implements Nested {
|
||||
protected String fileName;
|
||||
protected Date modificationDate;
|
||||
protected StreamEncoding format;
|
||||
|
||||
public LiteralData() {
|
||||
this("", new Date(0L), StreamEncoding.BINARY);
|
||||
}
|
||||
|
||||
public LiteralData(String fileName, Date modificationDate, StreamEncoding format) {
|
||||
this.fileName = fileName;
|
||||
this.modificationDate = modificationDate;
|
||||
this.format = format;
|
||||
}
|
||||
|
||||
public String getFileName() {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public Date getModificationDate() {
|
||||
return modificationDate;
|
||||
}
|
||||
|
||||
public StreamEncoding getFormat() {
|
||||
return format;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNestedChild() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static class CompressedData extends Layer implements Nested {
|
||||
protected final CompressionAlgorithm algorithm;
|
||||
|
||||
public CompressedData(CompressionAlgorithm zip, int depth) {
|
||||
super(depth);
|
||||
this.algorithm = zip;
|
||||
}
|
||||
|
||||
public CompressionAlgorithm getAlgorithm() {
|
||||
return algorithm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNestedChild() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static class EncryptedData extends Layer implements Nested {
|
||||
protected final SymmetricKeyAlgorithm algorithm;
|
||||
protected SubkeyIdentifier decryptionKey;
|
||||
protected SessionKey sessionKey;
|
||||
protected List<Long> recipients;
|
||||
|
||||
public EncryptedData(SymmetricKeyAlgorithm algorithm, int depth) {
|
||||
super(depth);
|
||||
this.algorithm = algorithm;
|
||||
}
|
||||
|
||||
public SymmetricKeyAlgorithm getAlgorithm() {
|
||||
return algorithm;
|
||||
}
|
||||
|
||||
public SessionKey getSessionKey() {
|
||||
return sessionKey;
|
||||
}
|
||||
|
||||
public List<Long> getRecipients() {
|
||||
return new ArrayList<>(recipients);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNestedChild() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private abstract static class LayerIterator<O> implements Iterator<O> {
|
||||
private Nested current;
|
||||
Layer last = null;
|
||||
Message parent;
|
||||
|
||||
LayerIterator(Message message) {
|
||||
super();
|
||||
this.parent = message;
|
||||
this.current = message.child;
|
||||
if (matches(current)) {
|
||||
last = (Layer) current;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
if (parent != null && matches(parent)) {
|
||||
return true;
|
||||
}
|
||||
if (last == null) {
|
||||
findNext();
|
||||
}
|
||||
return last != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public O next() {
|
||||
if (parent != null && matches(parent)) {
|
||||
O property = getProperty(parent);
|
||||
parent = null;
|
||||
return property;
|
||||
}
|
||||
if (last == null) {
|
||||
findNext();
|
||||
}
|
||||
if (last != null) {
|
||||
O property = getProperty(last);
|
||||
last = null;
|
||||
return property;
|
||||
}
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
|
||||
private void findNext() {
|
||||
while (current instanceof Layer) {
|
||||
current = ((Layer) current).child;
|
||||
if (matches(current)) {
|
||||
last = (Layer) current;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean matches(Nested layer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean matches(Layer layer) {
|
||||
if (layer instanceof Nested) {
|
||||
return matches((Nested) layer);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
abstract O getProperty(Layer last);
|
||||
}
|
||||
|
||||
}
|
|
@ -20,11 +20,13 @@ public interface MissingPublicKeyCallback {
|
|||
* you may not only search for the key-id on the key rings primary key!
|
||||
*
|
||||
* It would be super cool to provide the OpenPgp fingerprint here, but unfortunately one-pass-signatures
|
||||
* only contain the key id (see https://datatracker.ietf.org/doc/html/rfc4880#section-5.4)
|
||||
* only contain the key id.
|
||||
*
|
||||
* @param keyId ID of the missing signing (sub)key
|
||||
*
|
||||
* @return keyring containing the key or null
|
||||
*
|
||||
* @see <a href="https://datatracker.ietf.org/doc/html/rfc4880#section-5.4">RFC</a>
|
||||
*/
|
||||
@Nullable PGPPublicKeyRing onMissingPublicKeyEncountered(@Nonnull Long keyId);
|
||||
|
||||
|
|
|
@ -47,6 +47,7 @@ import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
|||
|
||||
public class OpenPgpInputStream extends BufferedInputStream {
|
||||
|
||||
@SuppressWarnings("CharsetObjectCanBeUsed")
|
||||
private static final byte[] ARMOR_HEADER = "-----BEGIN PGP ".getBytes(Charset.forName("UTF8"));
|
||||
|
||||
// Buffer beginning bytes of the data
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,215 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.decryption_verification;
|
||||
|
||||
import static org.pgpainless.signature.consumer.SignatureValidator.signatureWasCreatedInBounds;
|
||||
|
||||
import java.io.FilterInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPObjectFactory;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSignature;
|
||||
import org.bouncycastle.openpgp.PGPSignatureList;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.exception.SignatureValidationException;
|
||||
import org.pgpainless.policy.Policy;
|
||||
import org.pgpainless.signature.consumer.CertificateValidator;
|
||||
import org.pgpainless.signature.consumer.DetachedSignatureCheck;
|
||||
import org.pgpainless.signature.consumer.OnePassSignatureCheck;
|
||||
import org.pgpainless.signature.SignatureUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public abstract class SignatureInputStream extends FilterInputStream {
|
||||
|
||||
protected SignatureInputStream(InputStream inputStream) {
|
||||
super(inputStream);
|
||||
}
|
||||
|
||||
public static class VerifySignatures extends SignatureInputStream {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(VerifySignatures.class);
|
||||
|
||||
private final PGPObjectFactory objectFactory;
|
||||
private final List<OnePassSignatureCheck> opSignatures;
|
||||
private final Map<Long, OnePassSignatureCheck> opSignaturesWithMissingCert;
|
||||
private final List<DetachedSignatureCheck> detachedSignatures;
|
||||
private final ConsumerOptions options;
|
||||
private final OpenPgpMetadata.Builder resultBuilder;
|
||||
|
||||
public VerifySignatures(
|
||||
InputStream literalDataStream,
|
||||
@Nullable PGPObjectFactory objectFactory,
|
||||
List<OnePassSignatureCheck> opSignatures,
|
||||
Map<Long, OnePassSignatureCheck> onePassSignaturesWithMissingCert,
|
||||
List<DetachedSignatureCheck> detachedSignatures,
|
||||
ConsumerOptions options,
|
||||
OpenPgpMetadata.Builder resultBuilder) {
|
||||
super(literalDataStream);
|
||||
this.objectFactory = objectFactory;
|
||||
this.opSignatures = opSignatures;
|
||||
this.opSignaturesWithMissingCert = onePassSignaturesWithMissingCert;
|
||||
this.detachedSignatures = detachedSignatures;
|
||||
this.options = options;
|
||||
this.resultBuilder = resultBuilder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
final int data = super.read();
|
||||
final boolean endOfStream = data == -1;
|
||||
if (endOfStream) {
|
||||
finalizeSignatures();
|
||||
} else {
|
||||
byte b = (byte) data;
|
||||
updateOnePassSignatures(b);
|
||||
updateDetachedSignatures(b);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(@Nonnull byte[] b, int off, int len) throws IOException {
|
||||
int read = super.read(b, off, len);
|
||||
|
||||
final boolean endOfStream = read == -1;
|
||||
if (endOfStream) {
|
||||
finalizeSignatures();
|
||||
} else {
|
||||
updateOnePassSignatures(b, off, read);
|
||||
updateDetachedSignatures(b, off, read);
|
||||
}
|
||||
return read;
|
||||
}
|
||||
|
||||
private void finalizeSignatures() {
|
||||
parseAndCombineSignatures();
|
||||
verifyOnePassSignatures();
|
||||
verifyDetachedSignatures();
|
||||
}
|
||||
|
||||
public void parseAndCombineSignatures() {
|
||||
if (objectFactory == null) {
|
||||
return;
|
||||
}
|
||||
// Parse signatures from message
|
||||
PGPSignatureList signatures;
|
||||
try {
|
||||
signatures = parseSignatures(objectFactory);
|
||||
} catch (IOException e) {
|
||||
return;
|
||||
}
|
||||
List<PGPSignature> signatureList = SignatureUtils.toList(signatures);
|
||||
// Set signatures as comparison sigs in OPS checks
|
||||
for (int i = 0; i < opSignatures.size(); i++) {
|
||||
int reversedIndex = opSignatures.size() - i - 1;
|
||||
opSignatures.get(i).setSignature(signatureList.get(reversedIndex));
|
||||
}
|
||||
|
||||
for (PGPSignature signature : signatureList) {
|
||||
if (opSignaturesWithMissingCert.containsKey(signature.getKeyID())) {
|
||||
OnePassSignatureCheck check = opSignaturesWithMissingCert.remove(signature.getKeyID());
|
||||
check.setSignature(signature);
|
||||
|
||||
resultBuilder.addInvalidInbandSignature(new SignatureVerification(signature, null),
|
||||
new SignatureValidationException(
|
||||
"Missing verification certificate " + Long.toHexString(signature.getKeyID())));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private PGPSignatureList parseSignatures(PGPObjectFactory objectFactory) throws IOException {
|
||||
PGPSignatureList signatureList = null;
|
||||
Object pgpObject = objectFactory.nextObject();
|
||||
while (pgpObject != null && signatureList == null) {
|
||||
if (pgpObject instanceof PGPSignatureList) {
|
||||
signatureList = (PGPSignatureList) pgpObject;
|
||||
} else {
|
||||
pgpObject = objectFactory.nextObject();
|
||||
}
|
||||
}
|
||||
|
||||
if (signatureList == null || signatureList.isEmpty()) {
|
||||
throw new IOException("Verification failed - No Signatures found");
|
||||
}
|
||||
|
||||
return signatureList;
|
||||
}
|
||||
|
||||
|
||||
private synchronized void verifyOnePassSignatures() {
|
||||
Policy policy = PGPainless.getPolicy();
|
||||
for (OnePassSignatureCheck opSignature : opSignatures) {
|
||||
if (opSignature.getSignature() == null) {
|
||||
LOGGER.warn("Found OnePassSignature without respective signature packet -> skip");
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
signatureWasCreatedInBounds(options.getVerifyNotBefore(),
|
||||
options.getVerifyNotAfter()).verify(opSignature.getSignature());
|
||||
CertificateValidator.validateCertificateAndVerifyOnePassSignature(opSignature, policy);
|
||||
resultBuilder.addVerifiedInbandSignature(
|
||||
new SignatureVerification(opSignature.getSignature(), opSignature.getSigningKey()));
|
||||
} catch (SignatureValidationException e) {
|
||||
LOGGER.warn("One-pass-signature verification failed for signature made by key {}: {}",
|
||||
opSignature.getSigningKey(), e.getMessage(), e);
|
||||
resultBuilder.addInvalidInbandSignature(
|
||||
new SignatureVerification(opSignature.getSignature(), opSignature.getSigningKey()), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyDetachedSignatures() {
|
||||
Policy policy = PGPainless.getPolicy();
|
||||
for (DetachedSignatureCheck s : detachedSignatures) {
|
||||
try {
|
||||
signatureWasCreatedInBounds(options.getVerifyNotBefore(),
|
||||
options.getVerifyNotAfter()).verify(s.getSignature());
|
||||
CertificateValidator.validateCertificateAndVerifyInitializedSignature(s.getSignature(),
|
||||
(PGPPublicKeyRing) s.getSigningKeyRing(), policy);
|
||||
resultBuilder.addVerifiedDetachedSignature(new SignatureVerification(s.getSignature(),
|
||||
s.getSigningKeyIdentifier()));
|
||||
} catch (SignatureValidationException e) {
|
||||
LOGGER.warn("One-pass-signature verification failed for signature made by key {}: {}",
|
||||
s.getSigningKeyIdentifier(), e.getMessage(), e);
|
||||
resultBuilder.addInvalidDetachedSignature(new SignatureVerification(s.getSignature(),
|
||||
s.getSigningKeyIdentifier()), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateOnePassSignatures(byte data) {
|
||||
for (OnePassSignatureCheck opSignature : opSignatures) {
|
||||
opSignature.getOnePassSignature().update(data);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateOnePassSignatures(byte[] bytes, int offset, int length) {
|
||||
for (OnePassSignatureCheck opSignature : opSignatures) {
|
||||
opSignature.getOnePassSignature().update(bytes, offset, length);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateDetachedSignatures(byte b) {
|
||||
for (DetachedSignatureCheck detachedSignature : detachedSignatures) {
|
||||
detachedSignature.getSignature().update(b);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateDetachedSignatures(byte[] b, int off, int read) {
|
||||
for (DetachedSignatureCheck detachedSignature : detachedSignatures) {
|
||||
detachedSignature.getSignature().update(b, off, read);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,164 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.decryption_verification;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
import org.bouncycastle.bcpg.BCPGInputStream;
|
||||
import org.bouncycastle.bcpg.MarkerPacket;
|
||||
import org.bouncycastle.bcpg.Packet;
|
||||
import org.bouncycastle.openpgp.PGPCompressedData;
|
||||
import org.bouncycastle.openpgp.PGPEncryptedDataList;
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPLiteralData;
|
||||
import org.bouncycastle.openpgp.PGPOnePassSignature;
|
||||
import org.bouncycastle.openpgp.PGPSignature;
|
||||
import org.pgpainless.algorithm.OpenPgpPacket;
|
||||
|
||||
/**
|
||||
* Since we need to update signatures with data from the underlying stream, this class is used to tee out the data.
|
||||
* Unfortunately we cannot simply override {@link BCPGInputStream#read()} to tee the data out though, since
|
||||
* {@link BCPGInputStream#readPacket()} inconsistently calls a mix of {@link BCPGInputStream#read()} and
|
||||
* {@link InputStream#read()} of the underlying stream. This would cause the second length byte to get swallowed up.
|
||||
*
|
||||
* Therefore, this class delegates the teeing to an {@link DelayedTeeInputStream} which wraps the underlying
|
||||
* stream. Since calling {@link BCPGInputStream#nextPacketTag()} reads up to and including the next packets tag,
|
||||
* we need to delay teeing out that byte to signature verifiers.
|
||||
* Hence, the reading methods of the {@link TeeBCPGInputStream} handle pushing this byte to the output stream using
|
||||
* {@link DelayedTeeInputStream#squeeze()}.
|
||||
*/
|
||||
public class TeeBCPGInputStream {
|
||||
|
||||
protected final DelayedTeeInputStream delayedTee;
|
||||
// InputStream of OpenPGP packets of the current layer
|
||||
protected final BCPGInputStream packetInputStream;
|
||||
|
||||
public TeeBCPGInputStream(BCPGInputStream inputStream, OutputStream outputStream) {
|
||||
this.delayedTee = new DelayedTeeInputStream(inputStream, outputStream);
|
||||
this.packetInputStream = BCPGInputStream.wrap(delayedTee);
|
||||
}
|
||||
|
||||
public OpenPgpPacket nextPacketTag() throws IOException {
|
||||
int tag = packetInputStream.nextPacketTag();
|
||||
if (tag == -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
OpenPgpPacket packet;
|
||||
try {
|
||||
packet = OpenPgpPacket.requireFromTag(tag);
|
||||
} catch (NoSuchElementException e) {
|
||||
throw e;
|
||||
}
|
||||
return packet;
|
||||
}
|
||||
|
||||
public Packet readPacket() throws IOException {
|
||||
return packetInputStream.readPacket();
|
||||
}
|
||||
|
||||
public PGPCompressedData readCompressedData() throws IOException {
|
||||
delayedTee.squeeze();
|
||||
PGPCompressedData compressedData = new PGPCompressedData(packetInputStream);
|
||||
return compressedData;
|
||||
}
|
||||
|
||||
public PGPLiteralData readLiteralData() throws IOException {
|
||||
delayedTee.squeeze();
|
||||
return new PGPLiteralData(packetInputStream);
|
||||
}
|
||||
|
||||
public PGPEncryptedDataList readEncryptedDataList() throws IOException {
|
||||
delayedTee.squeeze();
|
||||
return new PGPEncryptedDataList(packetInputStream);
|
||||
}
|
||||
|
||||
public PGPOnePassSignature readOnePassSignature() throws PGPException, IOException {
|
||||
PGPOnePassSignature onePassSignature = new PGPOnePassSignature(packetInputStream);
|
||||
delayedTee.squeeze();
|
||||
return onePassSignature;
|
||||
}
|
||||
|
||||
public PGPSignature readSignature() throws PGPException, IOException {
|
||||
PGPSignature signature = new PGPSignature(packetInputStream);
|
||||
delayedTee.squeeze();
|
||||
return signature;
|
||||
}
|
||||
|
||||
public MarkerPacket readMarker() throws IOException {
|
||||
MarkerPacket markerPacket = (MarkerPacket) readPacket();
|
||||
delayedTee.squeeze();
|
||||
return markerPacket;
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
this.packetInputStream.close();
|
||||
}
|
||||
|
||||
public static class DelayedTeeInputStream extends InputStream {
|
||||
|
||||
private int last = -1;
|
||||
private final InputStream inputStream;
|
||||
private final OutputStream outputStream;
|
||||
|
||||
public DelayedTeeInputStream(InputStream inputStream, OutputStream outputStream) {
|
||||
this.inputStream = inputStream;
|
||||
this.outputStream = outputStream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
if (last != -1) {
|
||||
outputStream.write(last);
|
||||
}
|
||||
try {
|
||||
last = inputStream.read();
|
||||
return last;
|
||||
} catch (IOException e) {
|
||||
if ("crc check failed in armored message.".equals(e.getMessage())) {
|
||||
throw e;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
if (last != -1) {
|
||||
outputStream.write(last);
|
||||
}
|
||||
|
||||
int r = inputStream.read(b, off, len);
|
||||
if (r > 0) {
|
||||
outputStream.write(b, off, r - 1);
|
||||
last = b[off + r - 1];
|
||||
} else {
|
||||
last = -1;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* Squeeze the last byte out and update the output stream.
|
||||
*
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
public void squeeze() throws IOException {
|
||||
if (last != -1) {
|
||||
outputStream.write(last);
|
||||
}
|
||||
last = -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
inputStream.close();
|
||||
outputStream.close();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.decryption_verification.syntax_check;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPCompressedData;
|
||||
import org.bouncycastle.openpgp.PGPEncryptedDataList;
|
||||
import org.bouncycastle.openpgp.PGPLiteralData;
|
||||
import org.bouncycastle.openpgp.PGPOnePassSignatureList;
|
||||
import org.bouncycastle.openpgp.PGPSignatureList;
|
||||
|
||||
public enum InputSymbol {
|
||||
/**
|
||||
* A {@link PGPLiteralData} packet.
|
||||
*/
|
||||
LiteralData,
|
||||
/**
|
||||
* A {@link PGPSignatureList} object.
|
||||
*/
|
||||
Signature,
|
||||
/**
|
||||
* A {@link PGPOnePassSignatureList} object.
|
||||
*/
|
||||
OnePassSignature,
|
||||
/**
|
||||
* A {@link PGPCompressedData} packet.
|
||||
* The contents of this packet MUST form a valid OpenPGP message, so a nested PDA is opened to verify
|
||||
* its nested packet sequence.
|
||||
*/
|
||||
CompressedData,
|
||||
/**
|
||||
* A {@link PGPEncryptedDataList} object.
|
||||
* This object combines multiple ESKs and the corresponding Symmetrically Encrypted
|
||||
* (possibly Integrity Protected) Data packet.
|
||||
*/
|
||||
EncryptedData,
|
||||
/**
|
||||
* Marks the end of a (sub-) sequence.
|
||||
* This input is given if the end of an OpenPGP message is reached.
|
||||
* This might be the case for the end of the whole ciphertext, or the end of a packet with nested contents
|
||||
* (e.g. the end of a Compressed Data packet).
|
||||
*/
|
||||
EndOfSequence
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.decryption_verification.syntax_check;
|
||||
|
||||
import org.pgpainless.exception.MalformedOpenPgpMessageException;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* This class describes the syntax for OpenPGP messages as specified by rfc4880.
|
||||
*
|
||||
* @see <a href="https://www.rfc-editor.org/rfc/rfc4880#section-11.3">
|
||||
* rfc4880 - §11.3. OpenPGP Messages</a>
|
||||
* @see <a href="https://blog.jabberhead.tk/2022/09/14/using-pushdown-automata-to-verify-packet-sequences/">
|
||||
* Blog post about theoretic background and translation of grammar to PDA syntax</a>
|
||||
* @see <a href="https://blog.jabberhead.tk/2022/10/26/implementing-packet-sequence-validation-using-pushdown-automata/">
|
||||
* Blog post about practically implementing the PDA for packet syntax validation</a>
|
||||
*/
|
||||
public class OpenPgpMessageSyntax implements Syntax {
|
||||
|
||||
@Override
|
||||
public @Nonnull Transition transition(@Nonnull State from, @Nonnull InputSymbol input, @Nullable StackSymbol stackItem)
|
||||
throws MalformedOpenPgpMessageException {
|
||||
switch (from) {
|
||||
case OpenPgpMessage:
|
||||
return fromOpenPgpMessage(input, stackItem);
|
||||
case LiteralMessage:
|
||||
return fromLiteralMessage(input, stackItem);
|
||||
case CompressedMessage:
|
||||
return fromCompressedMessage(input, stackItem);
|
||||
case EncryptedMessage:
|
||||
return fromEncryptedMessage(input, stackItem);
|
||||
case Valid:
|
||||
return fromValid(input, stackItem);
|
||||
}
|
||||
|
||||
throw new MalformedOpenPgpMessageException(from, input, stackItem);
|
||||
}
|
||||
|
||||
Transition fromOpenPgpMessage(@Nonnull InputSymbol input, @Nullable StackSymbol stackItem)
|
||||
throws MalformedOpenPgpMessageException {
|
||||
if (stackItem != StackSymbol.msg) {
|
||||
throw new MalformedOpenPgpMessageException(State.OpenPgpMessage, input, stackItem);
|
||||
}
|
||||
|
||||
switch (input) {
|
||||
case LiteralData:
|
||||
return new Transition(State.LiteralMessage);
|
||||
|
||||
case Signature:
|
||||
return new Transition(State.OpenPgpMessage, StackSymbol.msg);
|
||||
|
||||
case OnePassSignature:
|
||||
return new Transition(State.OpenPgpMessage, StackSymbol.ops, StackSymbol.msg);
|
||||
|
||||
case CompressedData:
|
||||
return new Transition(State.CompressedMessage);
|
||||
|
||||
case EncryptedData:
|
||||
return new Transition(State.EncryptedMessage);
|
||||
|
||||
case EndOfSequence:
|
||||
default:
|
||||
throw new MalformedOpenPgpMessageException(State.OpenPgpMessage, input, stackItem);
|
||||
}
|
||||
}
|
||||
|
||||
Transition fromLiteralMessage(@Nonnull InputSymbol input, @Nullable StackSymbol stackItem)
|
||||
throws MalformedOpenPgpMessageException {
|
||||
switch (input) {
|
||||
case Signature:
|
||||
if (stackItem == StackSymbol.ops) {
|
||||
return new Transition(State.LiteralMessage);
|
||||
}
|
||||
break;
|
||||
|
||||
case EndOfSequence:
|
||||
if (stackItem == StackSymbol.terminus) {
|
||||
return new Transition(State.Valid);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
throw new MalformedOpenPgpMessageException(State.LiteralMessage, input, stackItem);
|
||||
}
|
||||
|
||||
Transition fromCompressedMessage(@Nonnull InputSymbol input, @Nullable StackSymbol stackItem)
|
||||
throws MalformedOpenPgpMessageException {
|
||||
switch (input) {
|
||||
case Signature:
|
||||
if (stackItem == StackSymbol.ops) {
|
||||
return new Transition(State.CompressedMessage);
|
||||
}
|
||||
break;
|
||||
|
||||
case EndOfSequence:
|
||||
if (stackItem == StackSymbol.terminus) {
|
||||
return new Transition(State.Valid);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
throw new MalformedOpenPgpMessageException(State.CompressedMessage, input, stackItem);
|
||||
}
|
||||
|
||||
Transition fromEncryptedMessage(@Nonnull InputSymbol input, @Nullable StackSymbol stackItem)
|
||||
throws MalformedOpenPgpMessageException {
|
||||
switch (input) {
|
||||
case Signature:
|
||||
if (stackItem == StackSymbol.ops) {
|
||||
return new Transition(State.EncryptedMessage);
|
||||
}
|
||||
break;
|
||||
|
||||
case EndOfSequence:
|
||||
if (stackItem == StackSymbol.terminus) {
|
||||
return new Transition(State.Valid);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
throw new MalformedOpenPgpMessageException(State.EncryptedMessage, input, stackItem);
|
||||
}
|
||||
|
||||
Transition fromValid(@Nonnull InputSymbol input, @Nullable StackSymbol stackItem)
|
||||
throws MalformedOpenPgpMessageException {
|
||||
// There is no applicable transition rule out of Valid
|
||||
throw new MalformedOpenPgpMessageException(State.Valid, input, stackItem);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.decryption_verification.syntax_check;
|
||||
|
||||
import org.pgpainless.exception.MalformedOpenPgpMessageException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Stack;
|
||||
|
||||
import static org.pgpainless.decryption_verification.syntax_check.StackSymbol.msg;
|
||||
import static org.pgpainless.decryption_verification.syntax_check.StackSymbol.terminus;
|
||||
|
||||
public class PDA {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(PDA.class);
|
||||
|
||||
// right now we implement what rfc4880 specifies.
|
||||
// TODO: Consider implementing what we proposed here:
|
||||
// https://mailarchive.ietf.org/arch/msg/openpgp/uepOF6XpSegMO4c59tt9e5H1i4g/
|
||||
private final Syntax syntax;
|
||||
private final Stack<StackSymbol> stack = new Stack<>();
|
||||
private final List<InputSymbol> inputs = new ArrayList<>(); // Track inputs for debugging / error reporting
|
||||
private State state;
|
||||
|
||||
/**
|
||||
* Default constructor which initializes the PDA to work with the {@link OpenPgpMessageSyntax}.
|
||||
*/
|
||||
public PDA() {
|
||||
this(new OpenPgpMessageSyntax(), State.OpenPgpMessage, terminus, msg);
|
||||
}
|
||||
|
||||
public PDA(@Nonnull Syntax syntax, @Nonnull State initialState, @Nonnull StackSymbol... initialStack) {
|
||||
this.syntax = syntax;
|
||||
this.state = initialState;
|
||||
for (StackSymbol symbol : initialStack) {
|
||||
pushStack(symbol);
|
||||
}
|
||||
}
|
||||
|
||||
public void next(InputSymbol input) throws MalformedOpenPgpMessageException {
|
||||
StackSymbol stackSymbol = popStack();
|
||||
try {
|
||||
Transition transition = syntax.transition(state, input, stackSymbol);
|
||||
state = transition.getNewState();
|
||||
for (StackSymbol item : transition.getPushedItems()) {
|
||||
pushStack(item);
|
||||
}
|
||||
inputs.add(input);
|
||||
} catch (MalformedOpenPgpMessageException e) {
|
||||
MalformedOpenPgpMessageException wrapped = new MalformedOpenPgpMessageException(
|
||||
"Malformed message: After reading stream " + Arrays.toString(inputs.toArray()) +
|
||||
", token '" + input + "' is not allowed." +
|
||||
"\nNo transition from state '" + state + "' with stack " + Arrays.toString(stack.toArray()) +
|
||||
(stackSymbol != null ? "||'" + stackSymbol + "'." : "."), e);
|
||||
LOGGER.debug("Invalid input '" + input + "'", wrapped);
|
||||
throw wrapped;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current state of the PDA.
|
||||
*
|
||||
* @return state
|
||||
*/
|
||||
public State getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public StackSymbol peekStack() {
|
||||
if (stack.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return stack.peek();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true, if the PDA is in a valid state (the OpenPGP message is valid).
|
||||
*
|
||||
* @return true if valid, false otherwise
|
||||
*/
|
||||
public boolean isValid() {
|
||||
return getState() == State.Valid && stack.isEmpty();
|
||||
}
|
||||
|
||||
public void assertValid() throws MalformedOpenPgpMessageException {
|
||||
if (!isValid()) {
|
||||
throw new MalformedOpenPgpMessageException("Pushdown Automaton is not in an acceptable state: " + toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pop an item from the stack.
|
||||
*
|
||||
* @return stack item
|
||||
*/
|
||||
private StackSymbol popStack() {
|
||||
if (stack.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return stack.pop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Push an item onto the stack.
|
||||
*
|
||||
* @param item item
|
||||
*/
|
||||
private void pushStack(StackSymbol item) {
|
||||
stack.push(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "State: " + state + " Stack: " + stack;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.decryption_verification.syntax_check;
|
||||
|
||||
public enum StackSymbol {
|
||||
/**
|
||||
* OpenPGP Message.
|
||||
*/
|
||||
msg,
|
||||
/**
|
||||
* OnePassSignature (in case of BC this represents a OnePassSignatureList).
|
||||
*/
|
||||
ops,
|
||||
/**
|
||||
* Special symbol representing the end of the message.
|
||||
*/
|
||||
terminus
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.decryption_verification.syntax_check;
|
||||
|
||||
/**
|
||||
* Set of states of the automaton.
|
||||
*/
|
||||
public enum State {
|
||||
OpenPgpMessage,
|
||||
LiteralMessage,
|
||||
CompressedMessage,
|
||||
EncryptedMessage,
|
||||
Valid
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.decryption_verification.syntax_check;
|
||||
|
||||
import org.pgpainless.exception.MalformedOpenPgpMessageException;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* This interface can be used to define a custom syntax for the {@link PDA}.
|
||||
*/
|
||||
public interface Syntax {
|
||||
|
||||
/**
|
||||
* Describe a transition rule from {@link State} <pre>from</pre> for {@link InputSymbol} <pre>input</pre>
|
||||
* with {@link StackSymbol} <pre>stackItem</pre> from the top of the {@link PDA PDAs} stack.
|
||||
* The resulting {@link Transition} contains the new {@link State}, as well as a list of
|
||||
* {@link StackSymbol StackSymbols} that get pushed onto the stack by the transition rule.
|
||||
* If there is no applicable rule, a {@link MalformedOpenPgpMessageException} is thrown, since in this case
|
||||
* the {@link InputSymbol} must be considered illegal.
|
||||
*
|
||||
* @param from current state of the PDA
|
||||
* @param input input symbol
|
||||
* @param stackItem item that got popped from the top of the stack
|
||||
* @return applicable transition rule containing the new state and pushed stack symbols
|
||||
* @throws MalformedOpenPgpMessageException if there is no applicable transition rule (the input symbol is illegal)
|
||||
*/
|
||||
@Nonnull Transition transition(@Nonnull State from, @Nonnull InputSymbol input, @Nullable StackSymbol stackItem)
|
||||
throws MalformedOpenPgpMessageException;
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.decryption_verification.syntax_check;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Result of applying a transition rule.
|
||||
* Transition rules can be described by implementing the {@link Syntax} interface.
|
||||
*/
|
||||
public class Transition {
|
||||
|
||||
private final List<StackSymbol> pushedItems = new ArrayList<>();
|
||||
private final State newState;
|
||||
|
||||
public Transition(@Nonnull State newState, @Nonnull StackSymbol... pushedItems) {
|
||||
this.newState = newState;
|
||||
this.pushedItems.addAll(Arrays.asList(pushedItems));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the new {@link State} that is reached by applying the transition.
|
||||
*
|
||||
* @return new state
|
||||
*/
|
||||
@Nonnull
|
||||
public State getNewState() {
|
||||
return newState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of {@link StackSymbol StackSymbols} that are pushed onto the stack
|
||||
* by applying the transition.
|
||||
* The list contains items in the order in which they are pushed onto the stack.
|
||||
* The list may be empty.
|
||||
*
|
||||
* @return list of items to be pushed onto the stack
|
||||
*/
|
||||
@Nonnull
|
||||
public List<StackSymbol> getPushedItems() {
|
||||
return new ArrayList<>(pushedItems);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/**
|
||||
* Pushdown Automaton to verify validity of packet sequences according to the OpenPGP Message format.
|
||||
*/
|
||||
package org.pgpainless.decryption_verification.syntax_check;
|
|
@ -1,17 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.exception;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Wrapper for {@link IOException} indicating that we need to throw this exception up.
|
||||
*/
|
||||
public class FinalIOException extends IOException {
|
||||
|
||||
public FinalIOException(IOException e) {
|
||||
super(e);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.exception;
|
||||
|
||||
import org.pgpainless.decryption_verification.syntax_check.InputSymbol;
|
||||
import org.pgpainless.decryption_verification.syntax_check.StackSymbol;
|
||||
import org.pgpainless.decryption_verification.syntax_check.State;
|
||||
|
||||
/**
|
||||
* Exception that gets thrown if the OpenPGP message is malformed.
|
||||
* Malformed messages are messages which do not follow the grammar specified in the RFC.
|
||||
*
|
||||
* @see <a href="https://www.rfc-editor.org/rfc/rfc4880#section-11.3">RFC4880 §11.3. OpenPGP Messages</a>
|
||||
*/
|
||||
public class MalformedOpenPgpMessageException extends RuntimeException {
|
||||
|
||||
public MalformedOpenPgpMessageException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public MalformedOpenPgpMessageException(State state, InputSymbol input, StackSymbol stackItem) {
|
||||
this("There is no legal transition from state '" + state + "' for input '" + input + "' when '" + stackItem + "' is on top of the stack.");
|
||||
}
|
||||
|
||||
public MalformedOpenPgpMessageException(String s, MalformedOpenPgpMessageException e) {
|
||||
super(s, e);
|
||||
}
|
||||
}
|
|
@ -1,17 +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;
|
||||
|
||||
/**
|
||||
* Exception that gets thrown if a {@link org.bouncycastle.bcpg.LiteralDataPacket} is expected, but not found.
|
||||
*/
|
||||
public class MissingLiteralDataException extends PGPException {
|
||||
|
||||
public MissingLiteralDataException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
|
@ -74,7 +74,7 @@ public class PGPKeyRingCollection {
|
|||
}
|
||||
|
||||
public PGPKeyRingCollection(@Nonnull Collection<PGPKeyRing> collection, boolean isSilent)
|
||||
throws IOException, PGPException {
|
||||
throws PGPException {
|
||||
List<PGPSecretKeyRing> secretKeyRings = new ArrayList<>();
|
||||
List<PGPPublicKeyRing> publicKeyRings = new ArrayList<>();
|
||||
|
||||
|
|
|
@ -1100,29 +1100,33 @@ public class KeyRingInfo {
|
|||
|
||||
List<PGPPublicKey> signingKeys = getSigningSubkeys();
|
||||
for (PGPPublicKey pk : signingKeys) {
|
||||
PGPSecretKey sk = getSecretKey(pk.getKeyID());
|
||||
if (sk == null) {
|
||||
// Missing secret key
|
||||
continue;
|
||||
}
|
||||
S2K s2K = sk.getS2K();
|
||||
// Unencrypted key
|
||||
if (s2K == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Secret key on smart-card
|
||||
int s2kType = s2K.getType();
|
||||
if (s2kType >= 100 && s2kType <= 110) {
|
||||
continue;
|
||||
}
|
||||
// protected secret key
|
||||
return true;
|
||||
return isSecretKeyAvailable(pk.getKeyID());
|
||||
}
|
||||
// No usable secret key found
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isSecretKeyAvailable(long keyId) {
|
||||
PGPSecretKey sk = getSecretKey(keyId);
|
||||
if (sk == null) {
|
||||
// Missing secret key
|
||||
return false;
|
||||
}
|
||||
S2K s2K = sk.getS2K();
|
||||
// Unencrypted key
|
||||
if (s2K == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Secret key on smart-card
|
||||
int s2kType = s2K.getType();
|
||||
if (s2kType >= 100 && s2kType <= 110) {
|
||||
return false;
|
||||
}
|
||||
// protected secret key
|
||||
return true;
|
||||
}
|
||||
|
||||
private KeyAccessor getKeyAccessor(@Nullable String userId, long keyID) {
|
||||
if (getPublicKey(keyID) == null) {
|
||||
throw new NoSuchElementException("No subkey with key id " + Long.toHexString(keyID) + " found on this key.");
|
||||
|
|
|
@ -327,7 +327,7 @@ public class SecretKeyRingEditor implements SecretKeyRingEditorInterface {
|
|||
@Nonnull SecretKeyRingProtector primaryKeyProtector,
|
||||
@Nonnull KeyFlag keyFlag,
|
||||
KeyFlag... additionalKeyFlags)
|
||||
throws PGPException, IOException, NoSuchAlgorithmException {
|
||||
throws PGPException, IOException {
|
||||
KeyFlag[] flags = concat(keyFlag, additionalKeyFlags);
|
||||
PublicKeyAlgorithm subkeyAlgorithm = PublicKeyAlgorithm.requireFromId(subkey.getPublicKey().getAlgorithm());
|
||||
SignatureSubpacketsUtil.assureKeyCanCarryFlags(subkeyAlgorithm);
|
||||
|
|
|
@ -41,7 +41,8 @@ public class KeyRingReader {
|
|||
* @return key ring
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
public PGPKeyRing keyRing(@Nonnull InputStream inputStream) throws IOException {
|
||||
public PGPKeyRing keyRing(@Nonnull InputStream inputStream)
|
||||
throws IOException {
|
||||
return readKeyRing(inputStream);
|
||||
}
|
||||
|
||||
|
@ -53,7 +54,8 @@ public class KeyRingReader {
|
|||
* @return key ring
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
public PGPKeyRing keyRing(@Nonnull byte[] bytes) throws IOException {
|
||||
public PGPKeyRing keyRing(@Nonnull byte[] bytes)
|
||||
throws IOException {
|
||||
return keyRing(new ByteArrayInputStream(bytes));
|
||||
}
|
||||
|
||||
|
@ -65,19 +67,23 @@ public class KeyRingReader {
|
|||
* @return key ring
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
public PGPKeyRing keyRing(@Nonnull String asciiArmored) throws IOException {
|
||||
public PGPKeyRing keyRing(@Nonnull String asciiArmored)
|
||||
throws IOException {
|
||||
return keyRing(asciiArmored.getBytes(UTF8));
|
||||
}
|
||||
|
||||
public PGPPublicKeyRing publicKeyRing(@Nonnull InputStream inputStream) throws IOException {
|
||||
public PGPPublicKeyRing publicKeyRing(@Nonnull InputStream inputStream)
|
||||
throws IOException {
|
||||
return readPublicKeyRing(inputStream);
|
||||
}
|
||||
|
||||
public PGPPublicKeyRing publicKeyRing(@Nonnull byte[] bytes) throws IOException {
|
||||
public PGPPublicKeyRing publicKeyRing(@Nonnull byte[] bytes)
|
||||
throws IOException {
|
||||
return publicKeyRing(new ByteArrayInputStream(bytes));
|
||||
}
|
||||
|
||||
public PGPPublicKeyRing publicKeyRing(@Nonnull String asciiArmored) throws IOException {
|
||||
public PGPPublicKeyRing publicKeyRing(@Nonnull String asciiArmored)
|
||||
throws IOException {
|
||||
return publicKeyRing(asciiArmored.getBytes(UTF8));
|
||||
}
|
||||
|
||||
|
@ -86,23 +92,28 @@ public class KeyRingReader {
|
|||
return readPublicKeyRingCollection(inputStream);
|
||||
}
|
||||
|
||||
public PGPPublicKeyRingCollection publicKeyRingCollection(@Nonnull byte[] bytes) throws IOException, PGPException {
|
||||
public PGPPublicKeyRingCollection publicKeyRingCollection(@Nonnull byte[] bytes)
|
||||
throws IOException, PGPException {
|
||||
return publicKeyRingCollection(new ByteArrayInputStream(bytes));
|
||||
}
|
||||
|
||||
public PGPPublicKeyRingCollection publicKeyRingCollection(@Nonnull String asciiArmored) throws IOException, PGPException {
|
||||
public PGPPublicKeyRingCollection publicKeyRingCollection(@Nonnull String asciiArmored)
|
||||
throws IOException, PGPException {
|
||||
return publicKeyRingCollection(asciiArmored.getBytes(UTF8));
|
||||
}
|
||||
|
||||
public PGPSecretKeyRing secretKeyRing(@Nonnull InputStream inputStream) throws IOException {
|
||||
public PGPSecretKeyRing secretKeyRing(@Nonnull InputStream inputStream)
|
||||
throws IOException {
|
||||
return readSecretKeyRing(inputStream);
|
||||
}
|
||||
|
||||
public PGPSecretKeyRing secretKeyRing(@Nonnull byte[] bytes) throws IOException {
|
||||
public PGPSecretKeyRing secretKeyRing(@Nonnull byte[] bytes)
|
||||
throws IOException {
|
||||
return secretKeyRing(new ByteArrayInputStream(bytes));
|
||||
}
|
||||
|
||||
public PGPSecretKeyRing secretKeyRing(@Nonnull String asciiArmored) throws IOException {
|
||||
public PGPSecretKeyRing secretKeyRing(@Nonnull String asciiArmored)
|
||||
throws IOException {
|
||||
return secretKeyRing(asciiArmored.getBytes(UTF8));
|
||||
}
|
||||
|
||||
|
@ -111,11 +122,13 @@ public class KeyRingReader {
|
|||
return readSecretKeyRingCollection(inputStream);
|
||||
}
|
||||
|
||||
public PGPSecretKeyRingCollection secretKeyRingCollection(@Nonnull byte[] bytes) throws IOException, PGPException {
|
||||
public PGPSecretKeyRingCollection secretKeyRingCollection(@Nonnull byte[] bytes)
|
||||
throws IOException, PGPException {
|
||||
return secretKeyRingCollection(new ByteArrayInputStream(bytes));
|
||||
}
|
||||
|
||||
public PGPSecretKeyRingCollection secretKeyRingCollection(@Nonnull String asciiArmored) throws IOException, PGPException {
|
||||
public PGPSecretKeyRingCollection secretKeyRingCollection(@Nonnull String asciiArmored)
|
||||
throws IOException, PGPException {
|
||||
return secretKeyRingCollection(asciiArmored.getBytes(UTF8));
|
||||
}
|
||||
|
||||
|
@ -124,11 +137,13 @@ public class KeyRingReader {
|
|||
return readKeyRingCollection(inputStream, isSilent);
|
||||
}
|
||||
|
||||
public PGPKeyRingCollection keyRingCollection(@Nonnull byte[] bytes, boolean isSilent) throws IOException, PGPException {
|
||||
public PGPKeyRingCollection keyRingCollection(@Nonnull byte[] bytes, boolean isSilent)
|
||||
throws IOException, PGPException {
|
||||
return keyRingCollection(new ByteArrayInputStream(bytes), isSilent);
|
||||
}
|
||||
|
||||
public PGPKeyRingCollection keyRingCollection(@Nonnull String asciiArmored, boolean isSilent) throws IOException, PGPException {
|
||||
public PGPKeyRingCollection keyRingCollection(@Nonnull String asciiArmored, boolean isSilent)
|
||||
throws IOException, PGPException {
|
||||
return keyRingCollection(asciiArmored.getBytes(UTF8), isSilent);
|
||||
}
|
||||
|
||||
|
@ -142,7 +157,8 @@ public class KeyRingReader {
|
|||
* @return key ring
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
public static PGPKeyRing readKeyRing(@Nonnull InputStream inputStream) throws IOException {
|
||||
public static PGPKeyRing readKeyRing(@Nonnull InputStream inputStream)
|
||||
throws IOException {
|
||||
return readKeyRing(inputStream, MAX_ITERATIONS);
|
||||
}
|
||||
|
||||
|
@ -157,7 +173,8 @@ public class KeyRingReader {
|
|||
* @return key ring
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
public static PGPKeyRing readKeyRing(@Nonnull InputStream inputStream, int maxIterations) throws IOException {
|
||||
public static PGPKeyRing readKeyRing(@Nonnull InputStream inputStream, int maxIterations)
|
||||
throws IOException {
|
||||
PGPObjectFactory objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory(
|
||||
ArmorUtils.getDecoderStream(inputStream));
|
||||
int i = 0;
|
||||
|
@ -181,7 +198,8 @@ public class KeyRingReader {
|
|||
throw new IOException("Loop exceeded max iteration count.");
|
||||
}
|
||||
|
||||
public static PGPPublicKeyRing readPublicKeyRing(@Nonnull InputStream inputStream) throws IOException {
|
||||
public static PGPPublicKeyRing readPublicKeyRing(@Nonnull InputStream inputStream)
|
||||
throws IOException {
|
||||
return readPublicKeyRing(inputStream, MAX_ITERATIONS);
|
||||
}
|
||||
|
||||
|
@ -196,7 +214,8 @@ public class KeyRingReader {
|
|||
*
|
||||
* @throws IOException in case of an IO error or exceeding of max iterations
|
||||
*/
|
||||
public static PGPPublicKeyRing readPublicKeyRing(@Nonnull InputStream inputStream, int maxIterations) throws IOException {
|
||||
public static PGPPublicKeyRing readPublicKeyRing(@Nonnull InputStream inputStream, int maxIterations)
|
||||
throws IOException {
|
||||
PGPObjectFactory objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory(
|
||||
ArmorUtils.getDecoderStream(inputStream));
|
||||
int i = 0;
|
||||
|
@ -218,7 +237,7 @@ public class KeyRingReader {
|
|||
}
|
||||
|
||||
public static PGPPublicKeyRingCollection readPublicKeyRingCollection(@Nonnull InputStream inputStream)
|
||||
throws IOException, PGPException {
|
||||
throws IOException {
|
||||
return readPublicKeyRingCollection(inputStream, MAX_ITERATIONS);
|
||||
}
|
||||
|
||||
|
@ -232,10 +251,9 @@ public class KeyRingReader {
|
|||
* @return public key ring collection
|
||||
*
|
||||
* @throws IOException in case of an IO error or exceeding of max iterations
|
||||
* @throws PGPException in case of a broken key
|
||||
*/
|
||||
public static PGPPublicKeyRingCollection readPublicKeyRingCollection(@Nonnull InputStream inputStream, int maxIterations)
|
||||
throws IOException, PGPException {
|
||||
throws IOException {
|
||||
PGPObjectFactory objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory(
|
||||
ArmorUtils.getDecoderStream(inputStream));
|
||||
|
||||
|
@ -265,7 +283,8 @@ public class KeyRingReader {
|
|||
throw new IOException("Loop exceeded max iteration count.");
|
||||
}
|
||||
|
||||
public static PGPSecretKeyRing readSecretKeyRing(@Nonnull InputStream inputStream) throws IOException {
|
||||
public static PGPSecretKeyRing readSecretKeyRing(@Nonnull InputStream inputStream)
|
||||
throws IOException {
|
||||
return readSecretKeyRing(inputStream, MAX_ITERATIONS);
|
||||
}
|
||||
|
||||
|
@ -280,7 +299,8 @@ public class KeyRingReader {
|
|||
*
|
||||
* @throws IOException in case of an IO error or exceeding of max iterations
|
||||
*/
|
||||
public static PGPSecretKeyRing readSecretKeyRing(@Nonnull InputStream inputStream, int maxIterations) throws IOException {
|
||||
public static PGPSecretKeyRing readSecretKeyRing(@Nonnull InputStream inputStream, int maxIterations)
|
||||
throws IOException {
|
||||
InputStream decoderStream = ArmorUtils.getDecoderStream(inputStream);
|
||||
PGPObjectFactory objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory(decoderStream);
|
||||
int i = 0;
|
||||
|
@ -303,7 +323,7 @@ public class KeyRingReader {
|
|||
}
|
||||
|
||||
public static PGPSecretKeyRingCollection readSecretKeyRingCollection(@Nonnull InputStream inputStream)
|
||||
throws IOException, PGPException {
|
||||
throws IOException {
|
||||
return readSecretKeyRingCollection(inputStream, MAX_ITERATIONS);
|
||||
}
|
||||
|
||||
|
@ -317,11 +337,10 @@ public class KeyRingReader {
|
|||
* @return secret key ring collection
|
||||
*
|
||||
* @throws IOException in case of an IO error or exceeding of max iterations
|
||||
* @throws PGPException in case of a broken secret key
|
||||
*/
|
||||
public static PGPSecretKeyRingCollection readSecretKeyRingCollection(@Nonnull InputStream inputStream,
|
||||
int maxIterations)
|
||||
throws IOException, PGPException {
|
||||
throws IOException {
|
||||
PGPObjectFactory objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory(
|
||||
ArmorUtils.getDecoderStream(inputStream));
|
||||
|
||||
|
|
|
@ -30,4 +30,8 @@ public final class KeyIdUtil {
|
|||
|
||||
return new BigInteger(longKeyId, 16).longValue();
|
||||
}
|
||||
|
||||
public static String formatKeyId(long keyId) {
|
||||
return Long.toHexString(keyId).toUpperCase();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -144,6 +144,17 @@ public final class KeyRingUtils {
|
|||
return secretKey;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public static PGPPublicKeyRing publicKeys(@Nonnull PGPKeyRing keys) {
|
||||
if (keys instanceof PGPPublicKeyRing) {
|
||||
return (PGPPublicKeyRing) keys;
|
||||
} else if (keys instanceof PGPSecretKeyRing) {
|
||||
return publicKeyRingFrom((PGPSecretKeyRing) keys);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unknown keys class: " + keys.getClass().getName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a {@link PGPPublicKeyRing} containing all public keys from the provided {@link PGPSecretKeyRing}.
|
||||
*
|
||||
|
@ -167,12 +178,9 @@ public final class KeyRingUtils {
|
|||
*
|
||||
* @param secretKeyRings secret key ring collection
|
||||
* @return public key ring collection
|
||||
* @throws PGPException TODO: remove
|
||||
* @throws IOException TODO: remove
|
||||
*/
|
||||
@Nonnull
|
||||
public static PGPPublicKeyRingCollection publicKeyRingCollectionFrom(@Nonnull PGPSecretKeyRingCollection secretKeyRings)
|
||||
throws PGPException, IOException {
|
||||
public static PGPPublicKeyRingCollection publicKeyRingCollectionFrom(@Nonnull PGPSecretKeyRingCollection secretKeyRings) {
|
||||
List<PGPPublicKeyRing> certificates = new ArrayList<>();
|
||||
for (PGPSecretKeyRing secretKey : secretKeyRings) {
|
||||
certificates.add(PGPainless.extractCertificate(secretKey));
|
||||
|
@ -200,13 +208,9 @@ public final class KeyRingUtils {
|
|||
*
|
||||
* @param rings array of public key rings
|
||||
* @return key ring collection
|
||||
*
|
||||
* @throws IOException in case of an io error
|
||||
* @throws PGPException in case of a broken key
|
||||
*/
|
||||
@Nonnull
|
||||
public static PGPPublicKeyRingCollection keyRingsToKeyRingCollection(@Nonnull PGPPublicKeyRing... rings)
|
||||
throws IOException, PGPException {
|
||||
public static PGPPublicKeyRingCollection keyRingsToKeyRingCollection(@Nonnull PGPPublicKeyRing... rings) {
|
||||
return new PGPPublicKeyRingCollection(Arrays.asList(rings));
|
||||
}
|
||||
|
||||
|
@ -215,13 +219,9 @@ public final class KeyRingUtils {
|
|||
*
|
||||
* @param rings array of secret key rings
|
||||
* @return secret key ring collection
|
||||
*
|
||||
* @throws IOException in case of an io error
|
||||
* @throws PGPException in case of a broken key
|
||||
*/
|
||||
@Nonnull
|
||||
public static PGPSecretKeyRingCollection keyRingsToKeyRingCollection(@Nonnull PGPSecretKeyRing... rings)
|
||||
throws IOException, PGPException {
|
||||
public static PGPSecretKeyRingCollection keyRingsToKeyRingCollection(@Nonnull PGPSecretKeyRing... rings) {
|
||||
return new PGPSecretKeyRingCollection(Arrays.asList(rings));
|
||||
}
|
||||
|
||||
|
|
|
@ -237,7 +237,7 @@ public class PublicKeyParameterValidationUtil {
|
|||
* Validate ElGamal public key parameters.
|
||||
*
|
||||
* Original implementation by the openpgpjs authors:
|
||||
* https://github.com/openpgpjs/openpgpjs/blob/main/src/crypto/public_key/elgamal.js#L76-L143
|
||||
* <a href="https://github.com/openpgpjs/openpgpjs/blob/main/src/crypto/public_key/elgamal.js#L76-L143>OpenPGP.js source</a>
|
||||
* @param secretKey secret key
|
||||
* @param publicKey public key
|
||||
* @return true if supposedly valid, false if invalid
|
||||
|
|
|
@ -13,19 +13,19 @@ import org.pgpainless.key.SubkeyIdentifier;
|
|||
* Tuple-class which bundles together a signature, the signing key that created the signature,
|
||||
* an identifier of the signing key and a record of whether the signature was verified.
|
||||
*/
|
||||
public class DetachedSignatureCheck {
|
||||
public class SignatureCheck {
|
||||
private final PGPSignature signature;
|
||||
private final PGPKeyRing signingKeyRing;
|
||||
private final SubkeyIdentifier signingKeyIdentifier;
|
||||
|
||||
/**
|
||||
* Create a new {@link DetachedSignatureCheck} object.
|
||||
* Create a new {@link SignatureCheck} object.
|
||||
*
|
||||
* @param signature signature
|
||||
* @param signingKeyRing signing key that created the signature
|
||||
* @param signingKeyIdentifier identifier of the used signing key
|
||||
*/
|
||||
public DetachedSignatureCheck(PGPSignature signature, PGPKeyRing signingKeyRing, SubkeyIdentifier signingKeyIdentifier) {
|
||||
public SignatureCheck(PGPSignature signature, PGPKeyRing signingKeyRing, SubkeyIdentifier signingKeyIdentifier) {
|
||||
this.signature = signature;
|
||||
this.signingKeyRing = signingKeyRing;
|
||||
this.signingKeyIdentifier = signingKeyIdentifier;
|
|
@ -178,6 +178,7 @@ public final class ArmorUtils {
|
|||
* If it is <pre>false</pre>, the signature will be encoded as-is.
|
||||
*
|
||||
* @param signature signature
|
||||
* @param export whether to exclude non-exportable subpackets or trust-packets.
|
||||
* @return ascii armored string
|
||||
*
|
||||
* @throws IOException in case of an error in the {@link ArmoredOutputStream}
|
||||
|
|
|
@ -1,54 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2018 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.util;
|
||||
|
||||
public final class BCUtil {
|
||||
|
||||
private BCUtil() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A constant time equals comparison - does not terminate early if
|
||||
* test will fail. For best results always pass the expected value
|
||||
* as the first parameter.
|
||||
*
|
||||
* TODO: This method was proposed as a patch to BC:
|
||||
* https://github.com/bcgit/bc-java/pull/1141
|
||||
* Replace usage of this method with upstream eventually.
|
||||
* Remove once BC 172 gets released, given it contains the patch.
|
||||
*
|
||||
* @param expected first array
|
||||
* @param supplied second array
|
||||
* @return true if arrays equal, false otherwise.
|
||||
*/
|
||||
public static boolean constantTimeAreEqual(
|
||||
char[] expected,
|
||||
char[] supplied) {
|
||||
if (expected == null || supplied == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (expected == supplied) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int len = Math.min(expected.length, supplied.length);
|
||||
|
||||
int nonEqual = expected.length ^ supplied.length;
|
||||
|
||||
// do the char-wise comparison
|
||||
for (int i = 0; i != len; i++) {
|
||||
nonEqual |= (expected[i] ^ supplied[i]);
|
||||
}
|
||||
// If supplied is longer than expected, iterate over rest of supplied with NOPs
|
||||
for (int i = len; i < supplied.length; i++) {
|
||||
nonEqual |= ((byte) supplied[i] ^ (byte) ~supplied[i]);
|
||||
}
|
||||
|
||||
return nonEqual == 0;
|
||||
}
|
||||
|
||||
}
|
|
@ -8,8 +8,6 @@ import javax.annotation.Nonnull;
|
|||
import javax.annotation.Nullable;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.pgpainless.util.BCUtil.constantTimeAreEqual;
|
||||
|
||||
public class Passphrase {
|
||||
|
||||
public final Object lock = new Object();
|
||||
|
@ -165,6 +163,6 @@ public class Passphrase {
|
|||
}
|
||||
Passphrase other = (Passphrase) obj;
|
||||
return (getChars() == null && other.getChars() == null) ||
|
||||
constantTimeAreEqual(getChars(), other.getChars());
|
||||
org.bouncycastle.util.Arrays.constantTimeAreEqual(getChars(), other.getChars());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,8 +4,7 @@
|
|||
|
||||
package investigations;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
|
@ -32,9 +31,8 @@ import org.pgpainless.algorithm.SignatureType;
|
|||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
||||
import org.pgpainless.decryption_verification.ConsumerOptions;
|
||||
import org.pgpainless.decryption_verification.DecryptionStream;
|
||||
import org.pgpainless.decryption_verification.OpenPgpMetadata;
|
||||
import org.pgpainless.exception.MalformedOpenPgpMessageException;
|
||||
import org.pgpainless.implementation.ImplementationFactory;
|
||||
import org.pgpainless.key.SubkeyIdentifier;
|
||||
import org.pgpainless.key.info.KeyRingInfo;
|
||||
import org.pgpainless.key.protection.UnlockSecretKey;
|
||||
import org.pgpainless.util.Passphrase;
|
||||
|
@ -177,7 +175,7 @@ public class InvestigateMultiSEIPMessageHandlingTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testDecryptAndVerifyDoesIgnoreAppendedSEIPData() throws IOException, PGPException {
|
||||
public void testDecryptAndVerifyDetectsAppendedSEIPData() throws IOException, PGPException {
|
||||
PGPSecretKeyRing ring1 = PGPainless.readKeyRing().secretKeyRing(KEY1);
|
||||
PGPSecretKeyRing ring2 = PGPainless.readKeyRing().secretKeyRing(KEY2);
|
||||
|
||||
|
@ -191,15 +189,6 @@ public class InvestigateMultiSEIPMessageHandlingTest {
|
|||
.withOptions(options);
|
||||
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
Streams.pipeAll(decryptionStream, out);
|
||||
decryptionStream.close();
|
||||
|
||||
assertArrayEquals(data1.getBytes(StandardCharsets.UTF_8), out.toByteArray());
|
||||
OpenPgpMetadata metadata = decryptionStream.getResult();
|
||||
assertEquals(1, metadata.getVerifiedSignatures().size(),
|
||||
"The first SEIP packet is signed exactly only by the signing key of ring1.");
|
||||
assertEquals(
|
||||
new SubkeyIdentifier(ring1, new KeyRingInfo(ring1).getSigningSubkeys().get(0).getKeyID()),
|
||||
metadata.getVerifiedSignatures().keySet().iterator().next());
|
||||
assertThrows(MalformedOpenPgpMessageException.class, () -> Streams.pipeAll(decryptionStream, out));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -489,7 +489,7 @@ public class AsciiArmorCRCTests {
|
|||
Passphrase passphrase = Passphrase.fromPassword("flowcrypt compatibility tests");
|
||||
|
||||
@Test
|
||||
public void testInvalidArmorCRCThrowsOnClose() throws PGPException, IOException {
|
||||
public void testInvalidArmorCRCThrowsOnClose() throws IOException {
|
||||
String message = "-----BEGIN PGP MESSAGE-----\n" +
|
||||
"Version: FlowCrypt 5.0.4 Gmail Encryption flowcrypt.com\n" +
|
||||
"Comment: Seamlessly send, receive and search encrypted email\n" +
|
||||
|
@ -542,14 +542,16 @@ public class AsciiArmorCRCTests {
|
|||
"-----END PGP MESSAGE-----\n";
|
||||
|
||||
PGPSecretKeyRing key = PGPainless.readKeyRing().secretKeyRing(ASCII_KEY);
|
||||
DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify()
|
||||
.onInputStream(new ByteArrayInputStream(message.getBytes(StandardCharsets.UTF_8)))
|
||||
.withOptions(new ConsumerOptions().addDecryptionKey(
|
||||
key, SecretKeyRingProtector.unlockAnyKeyWith(passphrase)
|
||||
));
|
||||
assertThrows(IOException.class, () -> {
|
||||
DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify()
|
||||
.onInputStream(new ByteArrayInputStream(message.getBytes(StandardCharsets.UTF_8)))
|
||||
.withOptions(new ConsumerOptions().addDecryptionKey(
|
||||
key, SecretKeyRingProtector.unlockAnyKeyWith(passphrase)
|
||||
));
|
||||
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
Streams.pipeAll(decryptionStream, outputStream);
|
||||
assertThrows(IOException.class, decryptionStream::close);
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
Streams.pipeAll(decryptionStream, outputStream);
|
||||
decryptionStream.close();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.bouncycastle;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPrivateKey;
|
||||
import org.bouncycastle.openpgp.PGPSecretKey;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.util.io.Streams;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.algorithm.EncryptionPurpose;
|
||||
import org.pgpainless.decryption_verification.ConsumerOptions;
|
||||
import org.pgpainless.decryption_verification.DecryptionStream;
|
||||
import org.pgpainless.key.SubkeyIdentifier;
|
||||
import org.pgpainless.key.info.KeyRingInfo;
|
||||
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
||||
import org.pgpainless.key.protection.UnlockSecretKey;
|
||||
|
||||
public class CachingBcPublicKeyDataDecryptorFactoryTest {
|
||||
|
||||
private static final String KEY = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" +
|
||||
"Version: PGPainless\n" +
|
||||
"Comment: C8AE 4279 5958 5F46 86A9 8B5F EC69 7C29 2BE4 44E0\n" +
|
||||
"Comment: Alice\n" +
|
||||
"\n" +
|
||||
"lFgEY1vEcxYJKwYBBAHaRw8BAQdAXOUK1uc1iBeM+mMt2nLCukXWoJd/SodrtN9S\n" +
|
||||
"U/zzwu0AAP9eePPw91KLuq6PF9jQoTRz/cW4CyiALNJpsOJIZ1rp3xOBtAVBbGlj\n" +
|
||||
"ZYiPBBMWCgBBBQJjW8RzCRDsaXwpK+RE4BYhBMiuQnlZWF9GhqmLX+xpfCkr5ETg\n" +
|
||||
"Ap4BApsBBRYCAwEABAsJCAcFFQoJCAsCmQEAAGqWAQC8oz7l8izjUis5ji+sgI+q\n" +
|
||||
"gML22VNybqmLBpzZwnNU5wEApe9fNTRbK5yAITGBscxH7o74Qe+CLI6Ni5MwzKxr\n" +
|
||||
"5AucXQRjW8RzEgorBgEEAZdVAQUBAQdAm8xk0QSvpp2ZU1KQ31E7eEZYLKpbW4JE\n" +
|
||||
"opmtMQx6AlIDAQgHAAD/XTb/qSosfkNvli3BQiUzVRAqKaU4PKAq7at6afxoYSgN\n" +
|
||||
"4Yh1BBgWCgAdBQJjW8RzAp4BApsMBRYCAwEABAsJCAcFFQoJCAsACgkQ7Gl8KSvk\n" +
|
||||
"ROB38QEA0MvDt0bjEXwFoM0E34z0MtPcG3VBYcQ+iFRIqFfEl5UA/2yZxFjoZqrs\n" +
|
||||
"AQE8TaVpXYfbc2p/GEKA9LGd9l/g0QQLnFgEY1vEcxYJKwYBBAHaRw8BAQdAyCOv\n" +
|
||||
"6hGUvHcCBSDKP3fRz+scyJ9zwMt7nFXK5A/k2YgAAQCn3Es+IhvePn3eBlcYMMr0\n" +
|
||||
"xcktrY1NJAIZPfjlUJ0J1g6LiNUEGBYKAH0FAmNbxHMCngECmwIFFgIDAQAECwkI\n" +
|
||||
"BwUVCgkIC18gBBkWCgAGBQJjW8RzAAoJECxLf7KoUc8wD18BANNpIr4E+RRVVztR\n" +
|
||||
"OVwdxSe0SRWGjkW8nHrRyghHKTuMAP9p4ZKicOYA1uZbiNNjyuJuS8xBH6Hihurb\n" +
|
||||
"gDypVgxdBQAKCRDsaXwpK+RE4EQjAP9ARZEPxKNLFkrvjoZ8nrts3qhv3VtMrU+9\n" +
|
||||
"huZnYLe1FQEAtgO6V7wutHvVARHXqPJ6lcv+SueIu+BjLFYEKuBwggs=\n" +
|
||||
"=ShJd\n" +
|
||||
"-----END PGP PRIVATE KEY BLOCK-----";
|
||||
|
||||
private static final String MSG = "-----BEGIN PGP MESSAGE-----\n" +
|
||||
"Version: PGPainless\n" +
|
||||
"\n" +
|
||||
"hF4DJmQMTBqw3G8SAQdALkHpO0UkS/CqkwxUz74MJU3PV72ZrIL8ZcrO8ofhblkw\n" +
|
||||
"iDIhSwwGTG3tj+sG+ZVWKsmONKi7Om5seJDHQtQ8MfdCELAgwYHSt6MrgDBhuDIH\n" +
|
||||
"0kABZhq2/8qk3EGXPpc+xxs4r4g8SgHOiiHSim5NGtounXXIaF6T/hUmlorkeYf/\n" +
|
||||
"a9pCC0QXRUAr8NOcdsfbvb5V\n" +
|
||||
"=dQa8\n" +
|
||||
"-----END PGP MESSAGE-----";
|
||||
|
||||
@Test
|
||||
public void test() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException {
|
||||
PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(KEY);
|
||||
SecretKeyRingProtector protector = SecretKeyRingProtector.unprotectedKeys();
|
||||
KeyRingInfo info = PGPainless.inspectKeyRing(secretKeys);
|
||||
SubkeyIdentifier decryptionKey = new SubkeyIdentifier(secretKeys,
|
||||
info.getEncryptionSubkeys(EncryptionPurpose.ANY).get(0).getKeyID());
|
||||
|
||||
PGPSecretKey secretKey = secretKeys.getSecretKey(decryptionKey.getSubkeyId());
|
||||
PGPPrivateKey privateKey = UnlockSecretKey.unlockSecretKey(secretKey, protector);
|
||||
CachingBcPublicKeyDataDecryptorFactory cachingFactory = new CachingBcPublicKeyDataDecryptorFactory(
|
||||
privateKey, decryptionKey);
|
||||
|
||||
ByteArrayInputStream ciphertextIn = new ByteArrayInputStream(MSG.getBytes());
|
||||
DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify()
|
||||
.onInputStream(ciphertextIn)
|
||||
.withOptions(ConsumerOptions.get()
|
||||
.addCustomDecryptorFactory(cachingFactory));
|
||||
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
Streams.pipeAll(decryptionStream, out);
|
||||
decryptionStream.close();
|
||||
|
||||
decryptionStream = PGPainless.decryptAndOrVerify()
|
||||
.onInputStream(ciphertextIn)
|
||||
.withOptions(ConsumerOptions.get()
|
||||
.addCustomDecryptorFactory(cachingFactory));
|
||||
out = new ByteArrayOutputStream();
|
||||
Streams.pipeAll(decryptionStream, out);
|
||||
decryptionStream.close();
|
||||
}
|
||||
}
|
|
@ -28,7 +28,7 @@ public class PGPPublicKeyRingTest {
|
|||
* Learning test to see if BC also makes userids available on subkeys.
|
||||
* It does not.
|
||||
*
|
||||
* see also https://security.stackexchange.com/questions/92635/is-it-possible-to-assign-different-uids-to-subkeys-for-the-purpose-of-having-mul
|
||||
* @see <a href="https://security.stackexchange.com/questions/92635/is-it-possible-to-assign-different-uids-to-subkeys-for-the-purpose-of-having-mul>Stackexchange link</a>
|
||||
*/
|
||||
@Test
|
||||
public void subkeysDoNotHaveUserIDsTest() throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException {
|
||||
|
|
|
@ -0,0 +1,277 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.gnupg;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.bouncycastle.bcpg.S2K;
|
||||
import org.bouncycastle.bcpg.SecretKeyPacket;
|
||||
import org.bouncycastle.openpgp.PGPSecretKey;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.key.SubkeyIdentifier;
|
||||
import org.pgpainless.key.util.KeyIdUtil;
|
||||
|
||||
public class GnuPGDummyKeyUtilTest {
|
||||
// normal, non-hw-backed key
|
||||
private static final String FULL_KEY = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" +
|
||||
"Version: PGPainless\n" +
|
||||
"Comment: 01FD AB6C E04A 5078 79FE 4A18 C312 C97D A9F7 6A4F\n" +
|
||||
"Comment: Hardy Hardware <hardy@hard.ware>\n" +
|
||||
"\n" +
|
||||
"lFgEY1vSiBYJKwYBBAHaRw8BAQdAQ58lZn/HOtg+1b1KS18odyQ6M4LaDdbJAyRf\n" +
|
||||
"eBwCeTQAAPwJN+Xmr0jjN7RA9jgqXnxC/rcWHmdp/j9NdEd7K2Wbxw/rtCBIYXJk\n" +
|
||||
"eSBIYXJkd2FyZSA8aGFyZHlAaGFyZC53YXJlPoiPBBMWCgBBBQJjW9KICRDDEsl9\n" +
|
||||
"qfdqTxYhBAH9q2zgSlB4ef5KGMMSyX2p92pPAp4BApsBBRYCAwEABAsJCAcFFQoJ\n" +
|
||||
"CAsCmQEAAPk2AP922T5TQ7hukFlpxX3ThMhieJnECGY5Eqt5U0/vEY1XdgD/eE1M\n" +
|
||||
"l9qqx6QGcaNKe8deMe3EhTant6mS9tqMHp2/3gmcXQRjW9KIEgorBgEEAZdVAQUB\n" +
|
||||
"AQdAVXBLNvNmFh9KX6iLmdNJM28Zc9PGnzEoAD9+T4p0lDwDAQgHAAD/fw9hnzeH\n" +
|
||||
"VtBaHi6efXvnc4rdVj8zWk0LKo1clFd3bTAN+oh1BBgWCgAdBQJjW9KIAp4BApsM\n" +
|
||||
"BRYCAwEABAsJCAcFFQoJCAsACgkQwxLJfan3ak/JyQD9GBj0vjtYZAf5Fi0eEKdi\n" +
|
||||
"Ags0yZrQPkMs6eL+83te770A/jG0DeJy+88fOfWTj+mixO98PZPnQ0MybWC/1QUT\n" +
|
||||
"vP0BnFgEY1vSiBYJKwYBBAHaRw8BAQdAvSYTD60t8vx10dSEBACUoIfVCpeOB30D\n" +
|
||||
"6nfwJtbDT0YAAQCgnCsN9iX7s2TQd8NPggWs4QdhaFpb6olt3SlAvUy/wRBDiNUE\n" +
|
||||
"GBYKAH0FAmNb0ogCngECmwIFFgIDAQAECwkIBwUVCgkIC18gBBkWCgAGBQJjW9KI\n" +
|
||||
"AAoJEJQCL6VtwFtJDmMBAKqsGfRFQxJXyPgugWBgEaO5lt9fMM0yUxa76cmSWe5f\n" +
|
||||
"AQD2oLSEW1GOgIs64+Z3gvtXopmeupT09HhI7ger98zDAwAKCRDDEsl9qfdqTwR6\n" +
|
||||
"AP9Xftw8xZ7/MWhYImk/xheqPy07K4qo3T1pGKUvUqjWQQEAhE3r0oTcJn+KVCwG\n" +
|
||||
"jF6AYiLOzO/R1x5bSlYD3FeJ3Qo=\n" +
|
||||
"=+vXp\n" +
|
||||
"-----END PGP PRIVATE KEY BLOCK-----\n";
|
||||
|
||||
private static final long primaryKeyId = KeyIdUtil.fromLongKeyId("C312C97DA9F76A4F");
|
||||
private static final long encryptionKeyId = KeyIdUtil.fromLongKeyId("6924D066714CE8C6");
|
||||
private static final long signatureKeyId = KeyIdUtil.fromLongKeyId("94022FA56DC05B49");
|
||||
private static final byte[] cardSerial = new byte[] {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f};
|
||||
|
||||
public static final String ALL_KEYS_ON_CARD = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" +
|
||||
"Version: PGPainless\n" +
|
||||
"Comment: 01FD AB6C E04A 5078 79FE 4A18 C312 C97D A9F7 6A4F\n" +
|
||||
"Comment: Hardy Hardware <hardy@hard.ware>\n" +
|
||||
"\n" +
|
||||
"lEwEY1vSiBYJKwYBBAHaRw8BAQdAQ58lZn/HOtg+1b1KS18odyQ6M4LaDdbJAyRf\n" +
|
||||
"eBwCeTT+AGUAR05VAhAAAQIDBAUGBwgJCgsMDQ4PtCBIYXJkeSBIYXJkd2FyZSA8\n" +
|
||||
"aGFyZHlAaGFyZC53YXJlPoiPBBMWCgBBBQJjW9KICRDDEsl9qfdqTxYhBAH9q2zg\n" +
|
||||
"SlB4ef5KGMMSyX2p92pPAp4BApsBBRYCAwEABAsJCAcFFQoJCAsCmQEAAPk2AP92\n" +
|
||||
"2T5TQ7hukFlpxX3ThMhieJnECGY5Eqt5U0/vEY1XdgD/eE1Ml9qqx6QGcaNKe8de\n" +
|
||||
"Me3EhTant6mS9tqMHp2/3gmcUQRjW9KIEgorBgEEAZdVAQUBAQdAVXBLNvNmFh9K\n" +
|
||||
"X6iLmdNJM28Zc9PGnzEoAD9+T4p0lDwDAQgH/gBlAEdOVQIQAAECAwQFBgcICQoL\n" +
|
||||
"DA0OD4h1BBgWCgAdBQJjW9KIAp4BApsMBRYCAwEABAsJCAcFFQoJCAsACgkQwxLJ\n" +
|
||||
"fan3ak/JyQD9GBj0vjtYZAf5Fi0eEKdiAgs0yZrQPkMs6eL+83te770A/jG0DeJy\n" +
|
||||
"+88fOfWTj+mixO98PZPnQ0MybWC/1QUTvP0BnEwEY1vSiBYJKwYBBAHaRw8BAQdA\n" +
|
||||
"vSYTD60t8vx10dSEBACUoIfVCpeOB30D6nfwJtbDT0b+AGUAR05VAhAAAQIDBAUG\n" +
|
||||
"BwgJCgsMDQ4PiNUEGBYKAH0FAmNb0ogCngECmwIFFgIDAQAECwkIBwUVCgkIC18g\n" +
|
||||
"BBkWCgAGBQJjW9KIAAoJEJQCL6VtwFtJDmMBAKqsGfRFQxJXyPgugWBgEaO5lt9f\n" +
|
||||
"MM0yUxa76cmSWe5fAQD2oLSEW1GOgIs64+Z3gvtXopmeupT09HhI7ger98zDAwAK\n" +
|
||||
"CRDDEsl9qfdqTwR6AP9Xftw8xZ7/MWhYImk/xheqPy07K4qo3T1pGKUvUqjWQQEA\n" +
|
||||
"hE3r0oTcJn+KVCwGjF6AYiLOzO/R1x5bSlYD3FeJ3Qo=\n" +
|
||||
"=rYoa\n" +
|
||||
"-----END PGP PRIVATE KEY BLOCK-----";
|
||||
|
||||
public static final String ALL_KEYS_REMOVED = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" +
|
||||
"Version: PGPainless\n" +
|
||||
"Comment: 01FD AB6C E04A 5078 79FE 4A18 C312 C97D A9F7 6A4F\n" +
|
||||
"Comment: Hardy Hardware <hardy@hard.ware>\n" +
|
||||
"\n" +
|
||||
"lDsEY1vSiBYJKwYBBAHaRw8BAQdAQ58lZn/HOtg+1b1KS18odyQ6M4LaDdbJAyRf\n" +
|
||||
"eBwCeTT+AGUAR05VAbQgSGFyZHkgSGFyZHdhcmUgPGhhcmR5QGhhcmQud2FyZT6I\n" +
|
||||
"jwQTFgoAQQUCY1vSiAkQwxLJfan3ak8WIQQB/ats4EpQeHn+ShjDEsl9qfdqTwKe\n" +
|
||||
"AQKbAQUWAgMBAAQLCQgHBRUKCQgLApkBAAD5NgD/dtk+U0O4bpBZacV904TIYniZ\n" +
|
||||
"xAhmORKreVNP7xGNV3YA/3hNTJfaqsekBnGjSnvHXjHtxIU2p7epkvbajB6dv94J\n" +
|
||||
"nEAEY1vSiBIKKwYBBAGXVQEFAQEHQFVwSzbzZhYfSl+oi5nTSTNvGXPTxp8xKAA/\n" +
|
||||
"fk+KdJQ8AwEIB/4AZQBHTlUBiHUEGBYKAB0FAmNb0ogCngECmwwFFgIDAQAECwkI\n" +
|
||||
"BwUVCgkICwAKCRDDEsl9qfdqT8nJAP0YGPS+O1hkB/kWLR4Qp2ICCzTJmtA+Qyzp\n" +
|
||||
"4v7ze17vvQD+MbQN4nL7zx859ZOP6aLE73w9k+dDQzJtYL/VBRO8/QGcOwRjW9KI\n" +
|
||||
"FgkrBgEEAdpHDwEBB0C9JhMPrS3y/HXR1IQEAJSgh9UKl44HfQPqd/Am1sNPRv4A\n" +
|
||||
"ZQBHTlUBiNUEGBYKAH0FAmNb0ogCngECmwIFFgIDAQAECwkIBwUVCgkIC18gBBkW\n" +
|
||||
"CgAGBQJjW9KIAAoJEJQCL6VtwFtJDmMBAKqsGfRFQxJXyPgugWBgEaO5lt9fMM0y\n" +
|
||||
"Uxa76cmSWe5fAQD2oLSEW1GOgIs64+Z3gvtXopmeupT09HhI7ger98zDAwAKCRDD\n" +
|
||||
"Esl9qfdqTwR6AP9Xftw8xZ7/MWhYImk/xheqPy07K4qo3T1pGKUvUqjWQQEAhE3r\n" +
|
||||
"0oTcJn+KVCwGjF6AYiLOzO/R1x5bSlYD3FeJ3Qo=\n" +
|
||||
"=GEN/\n" +
|
||||
"-----END PGP PRIVATE KEY BLOCK-----";
|
||||
|
||||
public static final String PRIMARY_KEY_ON_CARD = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" +
|
||||
"Version: PGPainless\n" +
|
||||
"Comment: 01FD AB6C E04A 5078 79FE 4A18 C312 C97D A9F7 6A4F\n" +
|
||||
"Comment: Hardy Hardware <hardy@hard.ware>\n" +
|
||||
"\n" +
|
||||
"lEwEY1vSiBYJKwYBBAHaRw8BAQdAQ58lZn/HOtg+1b1KS18odyQ6M4LaDdbJAyRf\n" +
|
||||
"eBwCeTT+AGUAR05VAhAAAQIDBAUGBwgJCgsMDQ4PtCBIYXJkeSBIYXJkd2FyZSA8\n" +
|
||||
"aGFyZHlAaGFyZC53YXJlPoiPBBMWCgBBBQJjW9KICRDDEsl9qfdqTxYhBAH9q2zg\n" +
|
||||
"SlB4ef5KGMMSyX2p92pPAp4BApsBBRYCAwEABAsJCAcFFQoJCAsCmQEAAPk2AP92\n" +
|
||||
"2T5TQ7hukFlpxX3ThMhieJnECGY5Eqt5U0/vEY1XdgD/eE1Ml9qqx6QGcaNKe8de\n" +
|
||||
"Me3EhTant6mS9tqMHp2/3gmcXQRjW9KIEgorBgEEAZdVAQUBAQdAVXBLNvNmFh9K\n" +
|
||||
"X6iLmdNJM28Zc9PGnzEoAD9+T4p0lDwDAQgHAAD/fw9hnzeHVtBaHi6efXvnc4rd\n" +
|
||||
"Vj8zWk0LKo1clFd3bTAN+oh1BBgWCgAdBQJjW9KIAp4BApsMBRYCAwEABAsJCAcF\n" +
|
||||
"FQoJCAsACgkQwxLJfan3ak/JyQD9GBj0vjtYZAf5Fi0eEKdiAgs0yZrQPkMs6eL+\n" +
|
||||
"83te770A/jG0DeJy+88fOfWTj+mixO98PZPnQ0MybWC/1QUTvP0BnFgEY1vSiBYJ\n" +
|
||||
"KwYBBAHaRw8BAQdAvSYTD60t8vx10dSEBACUoIfVCpeOB30D6nfwJtbDT0YAAQCg\n" +
|
||||
"nCsN9iX7s2TQd8NPggWs4QdhaFpb6olt3SlAvUy/wRBDiNUEGBYKAH0FAmNb0ogC\n" +
|
||||
"ngECmwIFFgIDAQAECwkIBwUVCgkIC18gBBkWCgAGBQJjW9KIAAoJEJQCL6VtwFtJ\n" +
|
||||
"DmMBAKqsGfRFQxJXyPgugWBgEaO5lt9fMM0yUxa76cmSWe5fAQD2oLSEW1GOgIs6\n" +
|
||||
"4+Z3gvtXopmeupT09HhI7ger98zDAwAKCRDDEsl9qfdqTwR6AP9Xftw8xZ7/MWhY\n" +
|
||||
"Imk/xheqPy07K4qo3T1pGKUvUqjWQQEAhE3r0oTcJn+KVCwGjF6AYiLOzO/R1x5b\n" +
|
||||
"SlYD3FeJ3Qo=\n" +
|
||||
"=zQLi\n" +
|
||||
"-----END PGP PRIVATE KEY BLOCK-----";
|
||||
|
||||
public static final String ENCRYPTION_KEY_ON_CARD = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" +
|
||||
"Version: PGPainless\n" +
|
||||
"Comment: 01FD AB6C E04A 5078 79FE 4A18 C312 C97D A9F7 6A4F\n" +
|
||||
"Comment: Hardy Hardware <hardy@hard.ware>\n" +
|
||||
"\n" +
|
||||
"lFgEY1vSiBYJKwYBBAHaRw8BAQdAQ58lZn/HOtg+1b1KS18odyQ6M4LaDdbJAyRf\n" +
|
||||
"eBwCeTQAAPwJN+Xmr0jjN7RA9jgqXnxC/rcWHmdp/j9NdEd7K2Wbxw/rtCBIYXJk\n" +
|
||||
"eSBIYXJkd2FyZSA8aGFyZHlAaGFyZC53YXJlPoiPBBMWCgBBBQJjW9KICRDDEsl9\n" +
|
||||
"qfdqTxYhBAH9q2zgSlB4ef5KGMMSyX2p92pPAp4BApsBBRYCAwEABAsJCAcFFQoJ\n" +
|
||||
"CAsCmQEAAPk2AP922T5TQ7hukFlpxX3ThMhieJnECGY5Eqt5U0/vEY1XdgD/eE1M\n" +
|
||||
"l9qqx6QGcaNKe8deMe3EhTant6mS9tqMHp2/3gmcUQRjW9KIEgorBgEEAZdVAQUB\n" +
|
||||
"AQdAVXBLNvNmFh9KX6iLmdNJM28Zc9PGnzEoAD9+T4p0lDwDAQgH/gBlAEdOVQIQ\n" +
|
||||
"AAECAwQFBgcICQoLDA0OD4h1BBgWCgAdBQJjW9KIAp4BApsMBRYCAwEABAsJCAcF\n" +
|
||||
"FQoJCAsACgkQwxLJfan3ak/JyQD9GBj0vjtYZAf5Fi0eEKdiAgs0yZrQPkMs6eL+\n" +
|
||||
"83te770A/jG0DeJy+88fOfWTj+mixO98PZPnQ0MybWC/1QUTvP0BnFgEY1vSiBYJ\n" +
|
||||
"KwYBBAHaRw8BAQdAvSYTD60t8vx10dSEBACUoIfVCpeOB30D6nfwJtbDT0YAAQCg\n" +
|
||||
"nCsN9iX7s2TQd8NPggWs4QdhaFpb6olt3SlAvUy/wRBDiNUEGBYKAH0FAmNb0ogC\n" +
|
||||
"ngECmwIFFgIDAQAECwkIBwUVCgkIC18gBBkWCgAGBQJjW9KIAAoJEJQCL6VtwFtJ\n" +
|
||||
"DmMBAKqsGfRFQxJXyPgugWBgEaO5lt9fMM0yUxa76cmSWe5fAQD2oLSEW1GOgIs6\n" +
|
||||
"4+Z3gvtXopmeupT09HhI7ger98zDAwAKCRDDEsl9qfdqTwR6AP9Xftw8xZ7/MWhY\n" +
|
||||
"Imk/xheqPy07K4qo3T1pGKUvUqjWQQEAhE3r0oTcJn+KVCwGjF6AYiLOzO/R1x5b\n" +
|
||||
"SlYD3FeJ3Qo=\n" +
|
||||
"=7OZu\n" +
|
||||
"-----END PGP PRIVATE KEY BLOCK-----";
|
||||
|
||||
public static final String SIGNATURE_KEY_ON_CARD = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" +
|
||||
"Version: PGPainless\n" +
|
||||
"Comment: 01FD AB6C E04A 5078 79FE 4A18 C312 C97D A9F7 6A4F\n" +
|
||||
"Comment: Hardy Hardware <hardy@hard.ware>\n" +
|
||||
"\n" +
|
||||
"lFgEY1vSiBYJKwYBBAHaRw8BAQdAQ58lZn/HOtg+1b1KS18odyQ6M4LaDdbJAyRf\n" +
|
||||
"eBwCeTQAAPwJN+Xmr0jjN7RA9jgqXnxC/rcWHmdp/j9NdEd7K2Wbxw/rtCBIYXJk\n" +
|
||||
"eSBIYXJkd2FyZSA8aGFyZHlAaGFyZC53YXJlPoiPBBMWCgBBBQJjW9KICRDDEsl9\n" +
|
||||
"qfdqTxYhBAH9q2zgSlB4ef5KGMMSyX2p92pPAp4BApsBBRYCAwEABAsJCAcFFQoJ\n" +
|
||||
"CAsCmQEAAPk2AP922T5TQ7hukFlpxX3ThMhieJnECGY5Eqt5U0/vEY1XdgD/eE1M\n" +
|
||||
"l9qqx6QGcaNKe8deMe3EhTant6mS9tqMHp2/3gmcXQRjW9KIEgorBgEEAZdVAQUB\n" +
|
||||
"AQdAVXBLNvNmFh9KX6iLmdNJM28Zc9PGnzEoAD9+T4p0lDwDAQgHAAD/fw9hnzeH\n" +
|
||||
"VtBaHi6efXvnc4rdVj8zWk0LKo1clFd3bTAN+oh1BBgWCgAdBQJjW9KIAp4BApsM\n" +
|
||||
"BRYCAwEABAsJCAcFFQoJCAsACgkQwxLJfan3ak/JyQD9GBj0vjtYZAf5Fi0eEKdi\n" +
|
||||
"Ags0yZrQPkMs6eL+83te770A/jG0DeJy+88fOfWTj+mixO98PZPnQ0MybWC/1QUT\n" +
|
||||
"vP0BnEwEY1vSiBYJKwYBBAHaRw8BAQdAvSYTD60t8vx10dSEBACUoIfVCpeOB30D\n" +
|
||||
"6nfwJtbDT0b+AGUAR05VAhAAAQIDBAUGBwgJCgsMDQ4PiNUEGBYKAH0FAmNb0ogC\n" +
|
||||
"ngECmwIFFgIDAQAECwkIBwUVCgkIC18gBBkWCgAGBQJjW9KIAAoJEJQCL6VtwFtJ\n" +
|
||||
"DmMBAKqsGfRFQxJXyPgugWBgEaO5lt9fMM0yUxa76cmSWe5fAQD2oLSEW1GOgIs6\n" +
|
||||
"4+Z3gvtXopmeupT09HhI7ger98zDAwAKCRDDEsl9qfdqTwR6AP9Xftw8xZ7/MWhY\n" +
|
||||
"Imk/xheqPy07K4qo3T1pGKUvUqjWQQEAhE3r0oTcJn+KVCwGjF6AYiLOzO/R1x5b\n" +
|
||||
"SlYD3FeJ3Qo=\n" +
|
||||
"=GpEw\n" +
|
||||
"-----END PGP PRIVATE KEY BLOCK-----";
|
||||
|
||||
@Test
|
||||
public void testMoveAllKeysToCard() throws IOException {
|
||||
PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(FULL_KEY);
|
||||
PGPSecretKeyRing expected = PGPainless.readKeyRing().secretKeyRing(ALL_KEYS_ON_CARD);
|
||||
|
||||
PGPSecretKeyRing onCard = GnuPGDummyKeyUtil.modify(secretKeys)
|
||||
.divertPrivateKeysToCard(GnuPGDummyKeyUtil.KeyFilter.any(), cardSerial);
|
||||
|
||||
for (PGPSecretKey key : onCard) {
|
||||
assertEquals(SecretKeyPacket.USAGE_SHA1, key.getS2KUsage());
|
||||
S2K s2K = key.getS2K();
|
||||
assertEquals(S2K.GNU_PROTECTION_MODE_DIVERT_TO_CARD, s2K.getProtectionMode());
|
||||
}
|
||||
|
||||
assertArrayEquals(expected.getEncoded(), onCard.getEncoded());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMovePrimaryKeyToCard() throws IOException {
|
||||
PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(FULL_KEY);
|
||||
PGPSecretKeyRing expected = PGPainless.readKeyRing().secretKeyRing(PRIMARY_KEY_ON_CARD);
|
||||
|
||||
PGPSecretKeyRing onCard = GnuPGDummyKeyUtil.modify(secretKeys)
|
||||
.divertPrivateKeysToCard(GnuPGDummyKeyUtil.KeyFilter.only(primaryKeyId), cardSerial);
|
||||
|
||||
assertArrayEquals(expected.getEncoded(), onCard.getEncoded());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMoveEncryptionKeyToCard() throws IOException {
|
||||
PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(FULL_KEY);
|
||||
PGPSecretKeyRing expected = PGPainless.readKeyRing().secretKeyRing(ENCRYPTION_KEY_ON_CARD);
|
||||
|
||||
PGPSecretKeyRing onCard = GnuPGDummyKeyUtil.modify(secretKeys)
|
||||
.divertPrivateKeysToCard(GnuPGDummyKeyUtil.KeyFilter.only(encryptionKeyId), cardSerial);
|
||||
|
||||
assertArrayEquals(expected.getEncoded(), onCard.getEncoded());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMoveSigningKeyToCard() throws IOException {
|
||||
PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(FULL_KEY);
|
||||
PGPSecretKeyRing expected = PGPainless.readKeyRing().secretKeyRing(SIGNATURE_KEY_ON_CARD);
|
||||
|
||||
PGPSecretKeyRing onCard = GnuPGDummyKeyUtil.modify(secretKeys)
|
||||
.divertPrivateKeysToCard(GnuPGDummyKeyUtil.KeyFilter.only(signatureKeyId), cardSerial);
|
||||
|
||||
assertArrayEquals(expected.getEncoded(), onCard.getEncoded());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveAllKeys() throws IOException {
|
||||
PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(FULL_KEY);
|
||||
PGPSecretKeyRing expected = PGPainless.readKeyRing().secretKeyRing(ALL_KEYS_REMOVED);
|
||||
|
||||
PGPSecretKeyRing removedSecretKeys = GnuPGDummyKeyUtil.modify(secretKeys)
|
||||
.removePrivateKeys(GnuPGDummyKeyUtil.KeyFilter.any());
|
||||
|
||||
for (PGPSecretKey key : removedSecretKeys) {
|
||||
assertEquals(key.getS2KUsage(), SecretKeyPacket.USAGE_SHA1);
|
||||
S2K s2k = key.getS2K();
|
||||
assertEquals(GnuPGDummyExtension.NO_PRIVATE_KEY.getId(), s2k.getProtectionMode());
|
||||
}
|
||||
|
||||
assertArrayEquals(expected.getEncoded(), removedSecretKeys.getEncoded());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSingleIdOfHardwareBackedKey() throws IOException {
|
||||
PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(FULL_KEY);
|
||||
assertTrue(GnuPGDummyKeyUtil.getIdsOfKeysWithGnuPGS2KDivertedToCard(secretKeys).isEmpty());
|
||||
|
||||
PGPSecretKeyRing withHardwareBackedEncryptionKey = GnuPGDummyKeyUtil.modify(secretKeys)
|
||||
.divertPrivateKeysToCard(GnuPGDummyKeyUtil.KeyFilter.only(encryptionKeyId));
|
||||
|
||||
Set<SubkeyIdentifier> hardwareBackedKeys = GnuPGDummyKeyUtil
|
||||
.getIdsOfKeysWithGnuPGS2KDivertedToCard(withHardwareBackedEncryptionKey);
|
||||
assertEquals(Collections.singleton(new SubkeyIdentifier(secretKeys, encryptionKeyId)), hardwareBackedKeys);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testGetIdsOfFullyHardwareBackedKey() throws IOException {
|
||||
PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(FULL_KEY);
|
||||
assertTrue(GnuPGDummyKeyUtil.getIdsOfKeysWithGnuPGS2KDivertedToCard(secretKeys).isEmpty());
|
||||
|
||||
PGPSecretKeyRing withHardwareBackedEncryptionKey = GnuPGDummyKeyUtil.modify(secretKeys)
|
||||
.divertPrivateKeysToCard(GnuPGDummyKeyUtil.KeyFilter.any());
|
||||
Set<SubkeyIdentifier> expected = new HashSet<>();
|
||||
for (PGPSecretKey key : secretKeys) {
|
||||
expected.add(new SubkeyIdentifier(secretKeys, key.getKeyID()));
|
||||
}
|
||||
|
||||
Set<SubkeyIdentifier> hardwareBackedKeys = GnuPGDummyKeyUtil
|
||||
.getIdsOfKeysWithGnuPGS2KDivertedToCard(withHardwareBackedEncryptionKey);
|
||||
|
||||
assertEquals(expected, hardwareBackedKeys);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.algorithm;
|
||||
|
||||
import org.bouncycastle.bcpg.PacketTags;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
public class OpenPgpPacketTest {
|
||||
|
||||
@Test
|
||||
public void testFromInvalidTag() {
|
||||
int tag = PacketTags.RESERVED;
|
||||
assertNull(OpenPgpPacket.fromTag(tag));
|
||||
assertThrows(NoSuchElementException.class,
|
||||
() -> OpenPgpPacket.requireFromTag(tag));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFromExistingTags() {
|
||||
for (OpenPgpPacket p : OpenPgpPacket.values()) {
|
||||
assertEquals(p, OpenPgpPacket.fromTag(p.getTag()));
|
||||
assertEquals(p, OpenPgpPacket.requireFromTag(p.getTag()));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPKESKTagMatches() {
|
||||
assertEquals(PacketTags.PUBLIC_KEY_ENC_SESSION, OpenPgpPacket.PKESK.getTag());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.decryption_verification;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPrivateKey;
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKey;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
|
||||
import org.bouncycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory;
|
||||
import org.bouncycastle.util.io.Streams;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.algorithm.EncryptionPurpose;
|
||||
import org.pgpainless.encryption_signing.EncryptionOptions;
|
||||
import org.pgpainless.encryption_signing.EncryptionStream;
|
||||
import org.pgpainless.encryption_signing.ProducerOptions;
|
||||
import org.pgpainless.key.SubkeyIdentifier;
|
||||
import org.pgpainless.key.info.KeyRingInfo;
|
||||
import org.pgpainless.key.protection.UnlockSecretKey;
|
||||
import org.pgpainless.util.Passphrase;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
public class CustomPublicKeyDataDecryptorFactoryTest {
|
||||
|
||||
@Test
|
||||
public void testDecryptionWithEmulatedHardwareDecryptionCallback()
|
||||
throws PGPException, IOException, InvalidAlgorithmParameterException, NoSuchAlgorithmException {
|
||||
PGPSecretKeyRing secretKey = PGPainless.generateKeyRing().modernKeyRing("Alice");
|
||||
PGPPublicKeyRing cert = PGPainless.extractCertificate(secretKey);
|
||||
KeyRingInfo info = PGPainless.inspectKeyRing(secretKey);
|
||||
PGPPublicKey encryptionKey = info.getEncryptionSubkeys(EncryptionPurpose.ANY).get(0);
|
||||
|
||||
// Encrypt a test message
|
||||
String plaintext = "Hello, World!\n";
|
||||
ByteArrayOutputStream ciphertextOut = new ByteArrayOutputStream();
|
||||
EncryptionStream encryptionStream = PGPainless.encryptAndOrSign()
|
||||
.onOutputStream(ciphertextOut)
|
||||
.withOptions(ProducerOptions.encrypt(EncryptionOptions.get()
|
||||
.addRecipient(cert)));
|
||||
encryptionStream.write(plaintext.getBytes(StandardCharsets.UTF_8));
|
||||
encryptionStream.close();
|
||||
|
||||
HardwareSecurity.DecryptionCallback hardwareDecryptionCallback = new HardwareSecurity.DecryptionCallback() {
|
||||
@Override
|
||||
public byte[] decryptSessionKey(long keyId, int keyAlgorithm, byte[] sessionKeyData)
|
||||
throws HardwareSecurity.HardwareSecurityException {
|
||||
// Emulate hardware decryption.
|
||||
try {
|
||||
PGPSecretKey decryptionKey = secretKey.getSecretKey(encryptionKey.getKeyID());
|
||||
PGPPrivateKey privateKey = UnlockSecretKey.unlockSecretKey(decryptionKey, Passphrase.emptyPassphrase());
|
||||
PublicKeyDataDecryptorFactory internal = new BcPublicKeyDataDecryptorFactory(privateKey);
|
||||
return internal.recoverSessionData(keyAlgorithm, new byte[][] {sessionKeyData});
|
||||
} catch (PGPException e) {
|
||||
throw new HardwareSecurity.HardwareSecurityException();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Decrypt
|
||||
DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify()
|
||||
.onInputStream(new ByteArrayInputStream(ciphertextOut.toByteArray()))
|
||||
.withOptions(ConsumerOptions.get()
|
||||
.addCustomDecryptorFactory(
|
||||
new HardwareSecurity.HardwareDataDecryptorFactory(
|
||||
new SubkeyIdentifier(cert, encryptionKey.getKeyID()),
|
||||
hardwareDecryptionCallback)));
|
||||
|
||||
ByteArrayOutputStream decryptedOut = new ByteArrayOutputStream();
|
||||
Streams.pipeAll(decryptionStream, decryptedOut);
|
||||
decryptionStream.close();
|
||||
|
||||
assertEquals(plaintext, decryptedOut.toString());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.decryption_verification;
|
||||
|
||||
import org.junit.JUtils;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.pgpainless.algorithm.CompressionAlgorithm;
|
||||
import org.pgpainless.algorithm.StreamEncoding;
|
||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
||||
import org.pgpainless.util.DateUtil;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class MessageMetadataTest {
|
||||
|
||||
@Test
|
||||
public void processTestMessage_COMP_ENC_ENC_LIT() {
|
||||
// Note: COMP of ENC does not make sense, since ENC is indistinguishable from randomness
|
||||
// and randomness cannot be encrypted.
|
||||
// For the sake of testing though, this is okay.
|
||||
MessageMetadata.Message message = new MessageMetadata.Message();
|
||||
|
||||
MessageMetadata.CompressedData compressedData = new MessageMetadata.CompressedData(CompressionAlgorithm.ZIP, message.depth + 1);
|
||||
MessageMetadata.EncryptedData encryptedData = new MessageMetadata.EncryptedData(SymmetricKeyAlgorithm.AES_128, compressedData.depth + 1);
|
||||
MessageMetadata.EncryptedData encryptedData1 = new MessageMetadata.EncryptedData(SymmetricKeyAlgorithm.AES_256, encryptedData.depth + 1);
|
||||
MessageMetadata.LiteralData literalData = new MessageMetadata.LiteralData();
|
||||
|
||||
message.setChild(compressedData);
|
||||
compressedData.setChild(encryptedData);
|
||||
encryptedData.setChild(encryptedData1);
|
||||
encryptedData1.setChild(literalData);
|
||||
|
||||
MessageMetadata metadata = new MessageMetadata(message);
|
||||
|
||||
// Check encryption algs
|
||||
assertEquals(SymmetricKeyAlgorithm.AES_128, metadata.getEncryptionAlgorithm(), "getEncryptionAlgorithm() returns alg of outermost EncryptedData");
|
||||
Iterator<SymmetricKeyAlgorithm> encryptionAlgs = metadata.getEncryptionAlgorithms();
|
||||
assertTrue(encryptionAlgs.hasNext(), "There is at least one EncryptedData child");
|
||||
assertTrue(encryptionAlgs.hasNext(), "The child is still there");
|
||||
assertEquals(SymmetricKeyAlgorithm.AES_128, encryptionAlgs.next(), "The first algo is AES128");
|
||||
assertTrue(encryptionAlgs.hasNext(), "There is another EncryptedData");
|
||||
assertTrue(encryptionAlgs.hasNext(), "There is *still* another EncryptedData");
|
||||
assertEquals(SymmetricKeyAlgorithm.AES_256, encryptionAlgs.next(), "The second algo is AES256");
|
||||
assertFalse(encryptionAlgs.hasNext(), "There is no more EncryptedData");
|
||||
assertFalse(encryptionAlgs.hasNext(), "There *still* is no more EncryptedData");
|
||||
|
||||
assertEquals(CompressionAlgorithm.ZIP, metadata.getCompressionAlgorithm(), "getCompressionAlgorithm() returns alg of outermost CompressedData");
|
||||
Iterator<CompressionAlgorithm> compAlgs = metadata.getCompressionAlgorithms();
|
||||
assertTrue(compAlgs.hasNext());
|
||||
assertTrue(compAlgs.hasNext());
|
||||
assertEquals(CompressionAlgorithm.ZIP, compAlgs.next());
|
||||
assertFalse(compAlgs.hasNext());
|
||||
assertFalse(compAlgs.hasNext());
|
||||
|
||||
assertEquals("", metadata.getFilename());
|
||||
JUtils.assertDateEquals(new Date(0L), metadata.getModificationDate());
|
||||
assertEquals(StreamEncoding.BINARY, metadata.getFormat());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcessLiteralDataMessage() {
|
||||
MessageMetadata.LiteralData literalData = new MessageMetadata.LiteralData(
|
||||
"collateral_murder.zip",
|
||||
DateUtil.parseUTCDate("2010-04-05 10:12:03 UTC"),
|
||||
StreamEncoding.BINARY);
|
||||
MessageMetadata.Message message = new MessageMetadata.Message();
|
||||
message.setChild(literalData);
|
||||
|
||||
MessageMetadata metadata = new MessageMetadata(message);
|
||||
assertNull(metadata.getCompressionAlgorithm());
|
||||
assertNull(metadata.getEncryptionAlgorithm());
|
||||
assertEquals("collateral_murder.zip", metadata.getFilename());
|
||||
assertEquals(DateUtil.parseUTCDate("2010-04-05 10:12:03 UTC"), metadata.getModificationDate());
|
||||
assertEquals(StreamEncoding.BINARY, metadata.getFormat());
|
||||
}
|
||||
}
|
|
@ -238,8 +238,10 @@ public class ModificationDetectionTests {
|
|||
);
|
||||
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
Streams.pipeAll(decryptionStream, out);
|
||||
assertThrows(ModificationDetectionException.class, decryptionStream::close);
|
||||
assertThrows(ModificationDetectionException.class, () -> {
|
||||
Streams.pipeAll(decryptionStream, out);
|
||||
decryptionStream.close();
|
||||
});
|
||||
}
|
||||
|
||||
@TestTemplate
|
||||
|
@ -269,8 +271,10 @@ public class ModificationDetectionTests {
|
|||
);
|
||||
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
Streams.pipeAll(decryptionStream, out);
|
||||
assertThrows(ModificationDetectionException.class, decryptionStream::close);
|
||||
assertThrows(ModificationDetectionException.class, () -> {
|
||||
Streams.pipeAll(decryptionStream, out);
|
||||
decryptionStream.close();
|
||||
});
|
||||
}
|
||||
|
||||
@TestTemplate
|
||||
|
@ -313,8 +317,10 @@ public class ModificationDetectionTests {
|
|||
);
|
||||
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
Streams.pipeAll(decryptionStream, out);
|
||||
assertThrows(ModificationDetectionException.class, decryptionStream::close);
|
||||
assertThrows(ModificationDetectionException.class, () -> {
|
||||
Streams.pipeAll(decryptionStream, out);
|
||||
decryptionStream.close();
|
||||
});
|
||||
}
|
||||
|
||||
@TestTemplate
|
||||
|
@ -344,8 +350,10 @@ public class ModificationDetectionTests {
|
|||
);
|
||||
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
Streams.pipeAll(decryptionStream, out);
|
||||
assertThrows(ModificationDetectionException.class, decryptionStream::close);
|
||||
assertThrows(ModificationDetectionException.class, () -> {
|
||||
Streams.pipeAll(decryptionStream, out);
|
||||
decryptionStream.close();
|
||||
});
|
||||
}
|
||||
|
||||
@TestTemplate
|
||||
|
@ -530,7 +538,7 @@ public class ModificationDetectionTests {
|
|||
);
|
||||
}
|
||||
|
||||
private PGPSecretKeyRingCollection getDecryptionKey() throws IOException, PGPException {
|
||||
private PGPSecretKeyRingCollection getDecryptionKey() throws IOException {
|
||||
PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(keyAscii);
|
||||
return new PGPSecretKeyRingCollection(Collections.singletonList(secretKeys));
|
||||
}
|
||||
|
|
|
@ -0,0 +1,687 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.decryption_verification;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.bouncycastle.bcpg.ArmoredInputStream;
|
||||
import org.bouncycastle.bcpg.ArmoredOutputStream;
|
||||
import org.bouncycastle.bcpg.CompressionAlgorithmTags;
|
||||
import org.bouncycastle.openpgp.PGPCompressedDataGenerator;
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPLiteralData;
|
||||
import org.bouncycastle.openpgp.PGPLiteralDataGenerator;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSignature;
|
||||
import org.bouncycastle.util.io.Streams;
|
||||
import org.junit.JUtils;
|
||||
import org.junit.jupiter.api.Named;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.algorithm.CompressionAlgorithm;
|
||||
import org.pgpainless.algorithm.StreamEncoding;
|
||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
||||
import org.pgpainless.encryption_signing.EncryptionOptions;
|
||||
import org.pgpainless.encryption_signing.EncryptionResult;
|
||||
import org.pgpainless.encryption_signing.EncryptionStream;
|
||||
import org.pgpainless.encryption_signing.ProducerOptions;
|
||||
import org.pgpainless.encryption_signing.SigningOptions;
|
||||
import org.pgpainless.exception.MalformedOpenPgpMessageException;
|
||||
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
||||
import org.pgpainless.util.ArmoredInputStreamFactory;
|
||||
import org.pgpainless.util.Passphrase;
|
||||
import org.pgpainless.util.Tuple;
|
||||
|
||||
public class OpenPgpMessageInputStreamTest {
|
||||
|
||||
public static final String KEY = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" +
|
||||
"Version: PGPainless\n" +
|
||||
"Comment: DA05 848F 37D4 68E6 F982 C889 7A70 1FC6 904D 3F4C\n" +
|
||||
"Comment: Alice <alice@pgpainless.org>\n" +
|
||||
"\n" +
|
||||
"lFgEYxzSCBYJKwYBBAHaRw8BAQdAeJU8m4GOJb1eQgv/ryilFHRfNLTYFMNqL6zj\n" +
|
||||
"r0vF7dsAAP42rAtngpJ6dZxoZlJX0Je65zk1VMPeTrXaWfPS2HSKBRGptBxBbGlj\n" +
|
||||
"ZSA8YWxpY2VAcGdwYWlubGVzcy5vcmc+iI8EExYKAEEFAmMc0ggJEHpwH8aQTT9M\n" +
|
||||
"FiEE2gWEjzfUaOb5gsiJenAfxpBNP0wCngECmwEFFgIDAQAECwkIBwUVCgkICwKZ\n" +
|
||||
"AQAApZEBALUXHtvswPZG28YO+16Men6/fpk+scvqpNMnD4ty3IkAAPwK6TuXjNnZ\n" +
|
||||
"0XuWdnilvLMV23Ai1d5g6em+lwLK5M2SApxdBGMc0ggSCisGAQQBl1UBBQEBB0D8\n" +
|
||||
"mNUVX8y2MXFaSeFYqOTPFnGT7dgNVdn6yc0UtkkHOgMBCAcAAP9y9OtP4SX9voPb\n" +
|
||||
"ID2u9PkJKgo4hTB8NK5LouGppdRtEBGriHUEGBYKAB0FAmMc0ggCngECmwwFFgID\n" +
|
||||
"AQAECwkIBwUVCgkICwAKCRB6cB/GkE0/TAywAQDpZRJS/joFH4+xcwheqWfI7ay/\n" +
|
||||
"WfojUoGQMYGnUjsgYwEAkceRUsgkqI0SVgYvuglfaQpZ9k2ns1mZGVLkXvu/yQyc\n" +
|
||||
"WARjHNIIFgkrBgEEAdpHDwEBB0BGN9BybSOrj8B6gim1SjbB/IiqAshlzMDunVkQ\n" +
|
||||
"X23npQABAJqvjOOY7qhBuTusC5/Q5+25iLrhMn4TI+LXlJHMVNOaE0OI1QQYFgoA\n" +
|
||||
"fQUCYxzSCAKeAQKbAgUWAgMBAAQLCQgHBRUKCQgLXyAEGRYKAAYFAmMc0ggACgkQ\n" +
|
||||
"KALh4BJQXl6yTQD/dh0N5228Uwtu7XHy6dmpMRX62cac5tXQ9WaDzpy8STgBAMdn\n" +
|
||||
"Mq948UOYEhdk/ZY2/hwux/4t+FHvqrXW8ziBe4cLAAoJEHpwH8aQTT9M1hQA/3Ms\n" +
|
||||
"P3kzoed3VsWu1ZMr7dKEngbc6SoJ2XPayzN0QYJaAQCIY5NcT9mZF97HWV3Vgeum\n" +
|
||||
"00sWMHXfkW3+nl5OpUZaDA==\n" +
|
||||
"=THgv\n" +
|
||||
"-----END PGP PRIVATE KEY BLOCK-----";
|
||||
|
||||
public static final String PLAINTEXT = "Hello, World!\n";
|
||||
public static final String PASSPHRASE = "sw0rdf1sh";
|
||||
|
||||
public static final String LIT = "" +
|
||||
"-----BEGIN PGP MESSAGE-----\n" +
|
||||
"Version: PGPainless\n" +
|
||||
"\n" +
|
||||
"yxRiAAAAAABIZWxsbywgV29ybGQhCg==\n" +
|
||||
"=WGju\n" +
|
||||
"-----END PGP MESSAGE-----";
|
||||
|
||||
public static final String LIT_LIT = "" +
|
||||
"-----BEGIN PGP MESSAGE-----\n" +
|
||||
"Version: BCPG v1.71\n" +
|
||||
"\n" +
|
||||
"yxRiAAAAAABIZWxsbywgV29ybGQhCssUYgAAAAAASGVsbG8sIFdvcmxkIQo=\n" +
|
||||
"=A91Q\n" +
|
||||
"-----END PGP MESSAGE-----";
|
||||
|
||||
public static final String COMP_LIT = "" +
|
||||
"-----BEGIN PGP MESSAGE-----\n" +
|
||||
"Version: BCPG v1.71\n" +
|
||||
"\n" +
|
||||
"owE7LZLEAAIeqTk5+ToK4flFOSmKXAA=\n" +
|
||||
"=ZYDg\n" +
|
||||
"-----END PGP MESSAGE-----";
|
||||
|
||||
public static final String COMP = "" +
|
||||
"-----BEGIN PGP MESSAGE-----\n" +
|
||||
"Version: BCPG v1.71\n" +
|
||||
"\n" +
|
||||
"owEDAA==\n" +
|
||||
"=MDzg\n" +
|
||||
"-----END PGP MESSAGE-----";
|
||||
|
||||
public static final String COMP_COMP_LIT = "" +
|
||||
"-----BEGIN PGP MESSAGE-----\n" +
|
||||
"Version: BCPG v1.71\n" +
|
||||
"\n" +
|
||||
"owEBRwC4/6MDQlpoOTFBWSZTWVuW2KAAAAr3hGAQBABgBABAAIAWBJAAAAggADFM\n" +
|
||||
"ABNBqBo00N6puqWR+TqInoXQ58XckU4UJBbltigA\n" +
|
||||
"=K9Zl\n" +
|
||||
"-----END PGP MESSAGE-----";
|
||||
|
||||
public static final String SIG_COMP_LIT = "" +
|
||||
"-----BEGIN PGP MESSAGE-----\n" +
|
||||
"Version: BCPG v1.71\n" +
|
||||
"\n" +
|
||||
"iHUEABYKACcFAmMc1i0JECgC4eASUF5eFiEEjN3RiJxCf/TyYOQjKALh4BJQXl4A\n" +
|
||||
"AHkrAP98uPpqrgIix7epgL7MM1cjXXGSxqbDfXHwgptk1YGQlgD/fw89VGcXwFaI\n" +
|
||||
"2k7kpXQYy/1BqnovM/jZ3X3mXhhTaAOjATstksQAAh6pOTn5Ogrh+UU5KYpcAA==\n" +
|
||||
"=WKPn\n" +
|
||||
"-----END PGP MESSAGE-----";
|
||||
|
||||
public static final String SENC_LIT = "" +
|
||||
"-----BEGIN PGP MESSAGE-----\n" +
|
||||
"Version: PGPainless\n" +
|
||||
"\n" +
|
||||
"jA0ECQMCuZ0qHNXWnGhg0j8Bdm1cxV65sYb7jDgb4rRMtdNpQ1dC4UpSYuk9YWS2\n" +
|
||||
"DpNEijbX8b/P1UOK2kJczNDADMRegZuLEI+dNsBnJjk=\n" +
|
||||
"=i4Y0\n" +
|
||||
"-----END PGP MESSAGE-----";
|
||||
|
||||
public static final String PENC_COMP_LIT = "" +
|
||||
"-----BEGIN PGP MESSAGE-----\n" +
|
||||
"Version: PGPainless\n" +
|
||||
"\n" +
|
||||
"hF4Dyqa/GWUy6WsSAQdAuGt49sQwdAHH3jPx11V3wSh7Amur3TbnONiQYJmMo3Qw\n" +
|
||||
"87yBnZCsaB7evxLBgi6PpF3tiytHM60xlrPeKKPpJhu60vNafRM2OOwqk7AdcZw4\n" +
|
||||
"0kYBEhiioO2btSuafNrQEjYzAgC7K6l7aPCcQObNp4ofryXu1P5vN+vUZp357hyS\n" +
|
||||
"6zZqP+0wJQ9yJZMvFTtFeSaSi0oMP2sb\n" +
|
||||
"=LvRL\n" +
|
||||
"-----END PGP MESSAGE-----";
|
||||
|
||||
public static final String OPS_LIT_SIG = "" +
|
||||
"-----BEGIN PGP MESSAGE-----\n" +
|
||||
"Version: PGPainless\n" +
|
||||
"\n" +
|
||||
"kA0DAAoWKALh4BJQXl4ByxRiAAAAAABIZWxsbywgV29ybGQhCoh1BAAWCgAnBQJj\n" +
|
||||
"I3fSCRAoAuHgElBeXhYhBIzd0YicQn/08mDkIygC4eASUF5eAADLOgEA766VyMMv\n" +
|
||||
"sxfQwQHly3T6ySHSNhYEpoyvdxVqhjBBR+EA/3i6C8lKFPPTh/PvTGbVFOl+eUSV\n" +
|
||||
"I0w3c+BRY/pO0m4H\n" +
|
||||
"=tkTV\n" +
|
||||
"-----END PGP MESSAGE-----";
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
// genLIT();
|
||||
// genLIT_LIT();
|
||||
// genCOMP_LIT();
|
||||
// genCOMP();
|
||||
// genCOMP_COMP_LIT();
|
||||
// genKey();
|
||||
// genSIG_LIT();
|
||||
// genSENC_LIT();
|
||||
genPENC_COMP_LIT();
|
||||
// genOPS_LIT_SIG();
|
||||
}
|
||||
|
||||
public static void genLIT() throws IOException {
|
||||
ArmoredOutputStream armorOut = new ArmoredOutputStream(System.out);
|
||||
PGPLiteralDataGenerator litGen = new PGPLiteralDataGenerator();
|
||||
OutputStream litOut = litGen.open(armorOut, PGPLiteralDataGenerator.BINARY, "", PGPLiteralData.NOW, new byte[1 << 9]);
|
||||
litOut.write(PLAINTEXT.getBytes(StandardCharsets.UTF_8));
|
||||
litOut.close();
|
||||
armorOut.close();
|
||||
}
|
||||
|
||||
public static void genLIT_LIT() throws IOException {
|
||||
ArmoredOutputStream armorOut = new ArmoredOutputStream(System.out);
|
||||
PGPLiteralDataGenerator litGen = new PGPLiteralDataGenerator();
|
||||
OutputStream litOut = litGen.open(armorOut, PGPLiteralDataGenerator.BINARY, "", PGPLiteralData.NOW, new byte[1 << 9]);
|
||||
litOut.write(PLAINTEXT.getBytes(StandardCharsets.UTF_8));
|
||||
litOut.close();
|
||||
|
||||
litOut = litGen.open(armorOut, PGPLiteralDataGenerator.BINARY, "", PGPLiteralData.NOW, new byte[1 << 9]);
|
||||
litOut.write(PLAINTEXT.getBytes(StandardCharsets.UTF_8));
|
||||
litOut.close();
|
||||
|
||||
armorOut.close();
|
||||
}
|
||||
|
||||
public static void genCOMP_LIT() throws IOException {
|
||||
ArmoredOutputStream armorOut = new ArmoredOutputStream(System.out);
|
||||
PGPCompressedDataGenerator compGen = new PGPCompressedDataGenerator(CompressionAlgorithmTags.ZIP);
|
||||
OutputStream compOut = compGen.open(armorOut);
|
||||
PGPLiteralDataGenerator litGen = new PGPLiteralDataGenerator();
|
||||
OutputStream litOut = litGen.open(compOut, PGPLiteralDataGenerator.BINARY, "", PGPLiteralData.NOW, new byte[1 << 9]);
|
||||
litOut.write(PLAINTEXT.getBytes(StandardCharsets.UTF_8));
|
||||
litOut.close();
|
||||
compOut.close();
|
||||
armorOut.close();
|
||||
}
|
||||
|
||||
public static void genCOMP() throws IOException {
|
||||
ArmoredOutputStream armorOut = new ArmoredOutputStream(System.out);
|
||||
PGPCompressedDataGenerator compGen = new PGPCompressedDataGenerator(CompressionAlgorithmTags.ZIP);
|
||||
OutputStream compOut = compGen.open(armorOut);
|
||||
compOut.close();
|
||||
armorOut.close();
|
||||
}
|
||||
|
||||
public static void genCOMP_COMP_LIT() throws IOException {
|
||||
ArmoredOutputStream armorOut = new ArmoredOutputStream(System.out);
|
||||
|
||||
PGPCompressedDataGenerator compGen1 = new PGPCompressedDataGenerator(CompressionAlgorithmTags.ZIP);
|
||||
OutputStream compOut1 = compGen1.open(armorOut);
|
||||
|
||||
PGPCompressedDataGenerator compGen2 = new PGPCompressedDataGenerator(CompressionAlgorithmTags.BZIP2);
|
||||
OutputStream compOut2 = compGen2.open(compOut1);
|
||||
|
||||
PGPLiteralDataGenerator litGen = new PGPLiteralDataGenerator();
|
||||
OutputStream litOut = litGen.open(compOut2, PGPLiteralDataGenerator.BINARY, "", PGPLiteralDataGenerator.NOW, new byte[1 << 9]);
|
||||
|
||||
litOut.write(PLAINTEXT.getBytes(StandardCharsets.UTF_8));
|
||||
litOut.close();
|
||||
compOut2.close();
|
||||
compOut1.close();
|
||||
armorOut.close();
|
||||
}
|
||||
|
||||
public static void genKey() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException {
|
||||
PGPainless.asciiArmor(
|
||||
PGPainless.generateKeyRing().modernKeyRing("Alice <alice@pgpainless.org>"),
|
||||
System.out);
|
||||
}
|
||||
|
||||
public static void genSIG_COMP_LIT() throws PGPException, IOException {
|
||||
PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(KEY);
|
||||
ByteArrayOutputStream msgOut = new ByteArrayOutputStream();
|
||||
EncryptionStream signer = PGPainless.encryptAndOrSign()
|
||||
.onOutputStream(msgOut)
|
||||
.withOptions(
|
||||
ProducerOptions.sign(
|
||||
SigningOptions.get()
|
||||
.addDetachedSignature(SecretKeyRingProtector.unprotectedKeys(), secretKeys)
|
||||
).setAsciiArmor(false)
|
||||
);
|
||||
|
||||
Streams.pipeAll(new ByteArrayInputStream(PLAINTEXT.getBytes(StandardCharsets.UTF_8)), signer);
|
||||
signer.close();
|
||||
EncryptionResult result = signer.getResult();
|
||||
PGPSignature detachedSignature = result.getDetachedSignatures().get(result.getDetachedSignatures().keySet().iterator().next()).iterator().next();
|
||||
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
ArmoredOutputStream armorOut = new ArmoredOutputStream(out);
|
||||
armorOut.flush();
|
||||
detachedSignature.encode(armorOut);
|
||||
armorOut.write(msgOut.toByteArray());
|
||||
armorOut.close();
|
||||
|
||||
String armored = out.toString();
|
||||
// CHECKSTYLE:OFF
|
||||
System.out.println(armored
|
||||
.replace("-----BEGIN PGP SIGNATURE-----\n", "-----BEGIN PGP MESSAGE-----\n")
|
||||
.replace("-----END PGP SIGNATURE-----", "-----END PGP MESSAGE-----"));
|
||||
// CHECKSTYLE:ON
|
||||
}
|
||||
|
||||
public static void genSENC_LIT() throws PGPException, IOException {
|
||||
EncryptionStream enc = PGPainless.encryptAndOrSign()
|
||||
.onOutputStream(System.out)
|
||||
.withOptions(ProducerOptions.encrypt(EncryptionOptions.get()
|
||||
.addPassphrase(Passphrase.fromPassword(PASSPHRASE)))
|
||||
.overrideCompressionAlgorithm(CompressionAlgorithm.UNCOMPRESSED));
|
||||
enc.write(PLAINTEXT.getBytes(StandardCharsets.UTF_8));
|
||||
enc.close();
|
||||
}
|
||||
|
||||
public static void genPENC_COMP_LIT() throws IOException, PGPException {
|
||||
PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(KEY);
|
||||
PGPPublicKeyRing cert = PGPainless.extractCertificate(secretKeys);
|
||||
EncryptionStream enc = PGPainless.encryptAndOrSign()
|
||||
.onOutputStream(System.out)
|
||||
.withOptions(ProducerOptions.encrypt(EncryptionOptions.get()
|
||||
.addRecipient(cert))
|
||||
.overrideCompressionAlgorithm(CompressionAlgorithm.ZLIB));
|
||||
|
||||
Streams.pipeAll(new ByteArrayInputStream(PLAINTEXT.getBytes(StandardCharsets.UTF_8)), enc);
|
||||
enc.close();
|
||||
}
|
||||
|
||||
public static void genOPS_LIT_SIG() throws PGPException, IOException {
|
||||
PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(KEY);
|
||||
|
||||
EncryptionStream enc = PGPainless.encryptAndOrSign()
|
||||
.onOutputStream(System.out)
|
||||
.withOptions(ProducerOptions.sign(SigningOptions.get()
|
||||
.addSignature(SecretKeyRingProtector.unprotectedKeys(), secretKeys))
|
||||
.overrideCompressionAlgorithm(CompressionAlgorithm.UNCOMPRESSED));
|
||||
Streams.pipeAll(new ByteArrayInputStream(PLAINTEXT.getBytes(StandardCharsets.UTF_8)), enc);
|
||||
enc.close();
|
||||
}
|
||||
|
||||
interface Processor {
|
||||
Tuple<String, MessageMetadata> process(String armoredMessage, ConsumerOptions options) throws PGPException, IOException;
|
||||
}
|
||||
|
||||
private static Stream<Arguments> provideMessageProcessors() {
|
||||
return Stream.of(
|
||||
Arguments.of(Named.of("read(buf,off,len)", (Processor) OpenPgpMessageInputStreamTest::processReadBuffered)),
|
||||
Arguments.of(Named.of("read()", (Processor) OpenPgpMessageInputStreamTest::processReadSequential))
|
||||
);
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "Process LIT using {0}")
|
||||
@MethodSource("provideMessageProcessors")
|
||||
public void testProcessLIT(Processor processor) throws IOException, PGPException {
|
||||
Tuple<String, MessageMetadata> result = processor.process(LIT, ConsumerOptions.get());
|
||||
String plain = result.getA();
|
||||
assertEquals(PLAINTEXT, plain);
|
||||
|
||||
MessageMetadata metadata = result.getB();
|
||||
assertNull(metadata.getCompressionAlgorithm());
|
||||
assertNull(metadata.getEncryptionAlgorithm());
|
||||
assertEquals("", metadata.getFilename());
|
||||
JUtils.assertDateEquals(new Date(0L), metadata.getModificationDate());
|
||||
assertEquals(StreamEncoding.BINARY, metadata.getFormat());
|
||||
assertTrue(metadata.getVerifiedInlineSignatures().isEmpty());
|
||||
assertTrue(metadata.getRejectedInlineSignatures().isEmpty());
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "Process LIT LIT using {0}")
|
||||
@MethodSource("provideMessageProcessors")
|
||||
public void testProcessLIT_LIT_fails(Processor processor) {
|
||||
assertThrows(MalformedOpenPgpMessageException.class,
|
||||
() -> processor.process(LIT_LIT, ConsumerOptions.get()));
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "Process COMP(LIT) using {0}")
|
||||
@MethodSource("provideMessageProcessors")
|
||||
public void testProcessCOMP_LIT(Processor processor)
|
||||
throws PGPException, IOException {
|
||||
Tuple<String, MessageMetadata> result = processor.process(COMP_LIT, ConsumerOptions.get());
|
||||
String plain = result.getA();
|
||||
assertEquals(PLAINTEXT, plain);
|
||||
MessageMetadata metadata = result.getB();
|
||||
assertEquals(CompressionAlgorithm.ZIP, metadata.getCompressionAlgorithm());
|
||||
assertTrue(metadata.getVerifiedInlineSignatures().isEmpty());
|
||||
assertTrue(metadata.getRejectedInlineSignatures().isEmpty());
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "Process COMP using {0}")
|
||||
@MethodSource("provideMessageProcessors")
|
||||
public void testProcessCOMP_fails(Processor processor) {
|
||||
assertThrows(MalformedOpenPgpMessageException.class,
|
||||
() -> processor.process(COMP, ConsumerOptions.get()));
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "Process COMP(COMP(LIT)) using {0}")
|
||||
@MethodSource("provideMessageProcessors")
|
||||
public void testProcessCOMP_COMP_LIT(Processor processor)
|
||||
throws PGPException, IOException {
|
||||
Tuple<String, MessageMetadata> result = processor.process(COMP_COMP_LIT, ConsumerOptions.get());
|
||||
String plain = result.getA();
|
||||
assertEquals(PLAINTEXT, plain);
|
||||
MessageMetadata metadata = result.getB();
|
||||
assertEquals(CompressionAlgorithm.ZIP, metadata.getCompressionAlgorithm());
|
||||
Iterator<CompressionAlgorithm> compressionAlgorithms = metadata.getCompressionAlgorithms();
|
||||
assertEquals(CompressionAlgorithm.ZIP, compressionAlgorithms.next());
|
||||
assertEquals(CompressionAlgorithm.BZIP2, compressionAlgorithms.next());
|
||||
assertFalse(compressionAlgorithms.hasNext());
|
||||
assertNull(metadata.getEncryptionAlgorithm());
|
||||
assertTrue(metadata.getVerifiedInlineSignatures().isEmpty());
|
||||
assertTrue(metadata.getRejectedInlineSignatures().isEmpty());
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "Process SIG COMP(LIT) using {0}")
|
||||
@MethodSource("provideMessageProcessors")
|
||||
public void testProcessSIG_COMP_LIT(Processor processor)
|
||||
throws PGPException, IOException {
|
||||
PGPPublicKeyRing cert = PGPainless.extractCertificate(
|
||||
PGPainless.readKeyRing().secretKeyRing(KEY));
|
||||
|
||||
Tuple<String, MessageMetadata> result = processor.process(SIG_COMP_LIT, ConsumerOptions.get()
|
||||
.addVerificationCert(cert));
|
||||
String plain = result.getA();
|
||||
assertEquals(PLAINTEXT, plain);
|
||||
MessageMetadata metadata = result.getB();
|
||||
assertEquals(CompressionAlgorithm.ZIP, metadata.getCompressionAlgorithm());
|
||||
assertNull(metadata.getEncryptionAlgorithm());
|
||||
assertFalse(metadata.getVerifiedInlineSignatures().isEmpty());
|
||||
assertTrue(metadata.getRejectedInlineSignatures().isEmpty());
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "Process SENC(LIT) using {0}")
|
||||
@MethodSource("provideMessageProcessors")
|
||||
public void testProcessSENC_LIT(Processor processor)
|
||||
throws PGPException, IOException {
|
||||
Tuple<String, MessageMetadata> result = processor.process(SENC_LIT, ConsumerOptions.get()
|
||||
.addDecryptionPassphrase(Passphrase.fromPassword(PASSPHRASE)));
|
||||
String plain = result.getA();
|
||||
assertEquals(PLAINTEXT, plain);
|
||||
MessageMetadata metadata = result.getB();
|
||||
assertNull(metadata.getCompressionAlgorithm());
|
||||
assertEquals(SymmetricKeyAlgorithm.AES_256, metadata.getEncryptionAlgorithm());
|
||||
assertTrue(metadata.getVerifiedInlineSignatures().isEmpty());
|
||||
assertTrue(metadata.getRejectedInlineSignatures().isEmpty());
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "Process PENC(COMP(LIT)) using {0}")
|
||||
@MethodSource("provideMessageProcessors")
|
||||
public void testProcessPENC_COMP_LIT(Processor processor)
|
||||
throws IOException, PGPException {
|
||||
PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(KEY);
|
||||
Tuple<String, MessageMetadata> result = processor.process(PENC_COMP_LIT, ConsumerOptions.get()
|
||||
.addDecryptionKey(secretKeys));
|
||||
String plain = result.getA();
|
||||
assertEquals(PLAINTEXT, plain);
|
||||
MessageMetadata metadata = result.getB();
|
||||
assertEquals(CompressionAlgorithm.ZLIB, metadata.getCompressionAlgorithm());
|
||||
assertEquals(SymmetricKeyAlgorithm.AES_256, metadata.getEncryptionAlgorithm());
|
||||
assertTrue(metadata.getVerifiedInlineSignatures().isEmpty());
|
||||
assertTrue(metadata.getRejectedInlineSignatures().isEmpty());
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "Process OPS LIT SIG using {0}")
|
||||
@MethodSource("provideMessageProcessors")
|
||||
public void testProcessOPS_LIT_SIG(Processor processor)
|
||||
throws IOException, PGPException {
|
||||
PGPPublicKeyRing cert = PGPainless.extractCertificate(PGPainless.readKeyRing().secretKeyRing(KEY));
|
||||
Tuple<String, MessageMetadata> result = processor.process(OPS_LIT_SIG, ConsumerOptions.get()
|
||||
.addVerificationCert(cert));
|
||||
String plain = result.getA();
|
||||
assertEquals(PLAINTEXT, plain);
|
||||
MessageMetadata metadata = result.getB();
|
||||
assertNull(metadata.getEncryptionAlgorithm());
|
||||
assertNull(metadata.getCompressionAlgorithm());
|
||||
assertFalse(metadata.getVerifiedInlineSignatures().isEmpty());
|
||||
assertTrue(metadata.getRejectedInlineSignatures().isEmpty());
|
||||
}
|
||||
|
||||
String BOB_KEY = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" +
|
||||
"Comment: Bob's OpenPGP Transferable Secret Key\n" +
|
||||
"\n" +
|
||||
"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\n" +
|
||||
"=miES\n" +
|
||||
"-----END PGP PRIVATE KEY BLOCK-----";
|
||||
|
||||
@ParameterizedTest(name = "Process PENC(OPS OPS LIT SIG SIG) using {0}")
|
||||
@MethodSource("provideMessageProcessors")
|
||||
public void testProcessPENC_OPS_OPS_LIT_SIG_SIG(Processor processor) throws IOException, PGPException {
|
||||
String MSG = "-----BEGIN PGP MESSAGE-----\n" +
|
||||
"\n" +
|
||||
"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\n" +
|
||||
"=mdYs\n" +
|
||||
"-----END PGP MESSAGE-----\n";
|
||||
PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(BOB_KEY);
|
||||
PGPPublicKeyRing certificate = PGPainless.extractCertificate(secretKeys);
|
||||
|
||||
Tuple<String, MessageMetadata> result = processor.process(MSG, ConsumerOptions.get()
|
||||
.addVerificationCert(certificate)
|
||||
.addDecryptionKey(secretKeys));
|
||||
String plain = result.getA();
|
||||
assertEquals("encrypt ∘ sign ∘ sign", plain);
|
||||
MessageMetadata metadata = result.getB();
|
||||
assertEquals(SymmetricKeyAlgorithm.AES_256, metadata.getEncryptionAlgorithm());
|
||||
assertNull(metadata.getCompressionAlgorithm());
|
||||
assertFalse(metadata.getVerifiedInlineSignatures().isEmpty());
|
||||
assertTrue(metadata.getRejectedInlineSignatures().isEmpty());
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "Process PENC(OPS OPS OPS LIT SIG SIG SIG) using {0}")
|
||||
@MethodSource("provideMessageProcessors")
|
||||
public void testProcessOPS_OPS_OPS_LIT_SIG_SIG_SIG(Processor processor) throws IOException, PGPException {
|
||||
String MSG = "-----BEGIN PGP MESSAGE-----\n" +
|
||||
"\n" +
|
||||
"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\n" +
|
||||
"=JP9T\n" +
|
||||
"-----END PGP MESSAGE-----";
|
||||
PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(BOB_KEY);
|
||||
PGPPublicKeyRing certificate = PGPainless.extractCertificate(secretKeys);
|
||||
|
||||
Tuple<String, MessageMetadata> result = processor.process(MSG, ConsumerOptions.get()
|
||||
.addVerificationCert(certificate)
|
||||
.addDecryptionKey(secretKeys));
|
||||
String plain = result.getA();
|
||||
assertEquals("encrypt ∘ sign ∘ sign ∘ sign", plain);
|
||||
MessageMetadata metadata = result.getB();
|
||||
assertEquals(SymmetricKeyAlgorithm.AES_256, metadata.getEncryptionAlgorithm());
|
||||
assertNull(metadata.getCompressionAlgorithm());
|
||||
assertFalse(metadata.getVerifiedInlineSignatures().isEmpty());
|
||||
assertTrue(metadata.getRejectedInlineSignatures().isEmpty());
|
||||
}
|
||||
|
||||
private static Tuple<String, MessageMetadata> processReadBuffered(String armoredMessage, ConsumerOptions options)
|
||||
throws PGPException, IOException {
|
||||
OpenPgpMessageInputStream in = get(armoredMessage, options);
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
Streams.pipeAll(in, out);
|
||||
in.close();
|
||||
MessageMetadata metadata = in.getMetadata();
|
||||
return new Tuple<>(out.toString(), metadata);
|
||||
}
|
||||
|
||||
private static Tuple<String, MessageMetadata> processReadSequential(String armoredMessage, ConsumerOptions options)
|
||||
throws PGPException, IOException {
|
||||
OpenPgpMessageInputStream in = get(armoredMessage, options);
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
|
||||
int r;
|
||||
while ((r = in.read()) != -1) {
|
||||
out.write(r);
|
||||
}
|
||||
|
||||
in.close();
|
||||
MessageMetadata metadata = in.getMetadata();
|
||||
return new Tuple<>(out.toString(), metadata);
|
||||
}
|
||||
|
||||
private static OpenPgpMessageInputStream get(String armored, ConsumerOptions options)
|
||||
throws IOException, PGPException {
|
||||
ByteArrayInputStream bytesIn = new ByteArrayInputStream(armored.getBytes(StandardCharsets.UTF_8));
|
||||
ArmoredInputStream armorIn = ArmoredInputStreamFactory.get(bytesIn);
|
||||
OpenPgpMessageInputStream pgpIn = OpenPgpMessageInputStream.create(armorIn, options);
|
||||
return pgpIn;
|
||||
}
|
||||
}
|
|
@ -185,7 +185,7 @@ public class PreventDecryptionUsingNonEncryptionKeyTest {
|
|||
decryptionStream.close();
|
||||
OpenPgpMetadata metadata = decryptionStream.getResult();
|
||||
|
||||
assertEquals(metadata.getDecryptionKey(), new SubkeyIdentifier(secretKeys, secretKeys.getPublicKey().getKeyID()));
|
||||
assertEquals(new SubkeyIdentifier(secretKeys, secretKeys.getPublicKey().getKeyID()), metadata.getDecryptionKey());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -11,12 +11,12 @@ import java.io.ByteArrayOutputStream;
|
|||
import java.io.IOException;
|
||||
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.TestTemplate;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.exception.MalformedOpenPgpMessageException;
|
||||
import org.pgpainless.util.TestAllImplementations;
|
||||
|
||||
public class RecursionDepthTest {
|
||||
|
@ -143,7 +143,7 @@ public class RecursionDepthTest {
|
|||
"-----END PGP ARMORED FILE-----\n";
|
||||
|
||||
|
||||
assertThrows(PGPException.class, () -> {
|
||||
assertThrows(MalformedOpenPgpMessageException.class, () -> {
|
||||
DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify()
|
||||
.onInputStream(new ByteArrayInputStream(msg.getBytes(StandardCharsets.UTF_8)))
|
||||
.withOptions(new ConsumerOptions().addDecryptionKey(secretKey));
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.decryption_verification;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.util.io.Streams;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.encryption_signing.EncryptionOptions;
|
||||
import org.pgpainless.encryption_signing.EncryptionStream;
|
||||
import org.pgpainless.encryption_signing.ProducerOptions;
|
||||
import org.gnupg.GnuPGDummyKeyUtil;
|
||||
import org.pgpainless.exception.MissingDecryptionMethodException;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
public class TryDecryptWithUnavailableGnuDummyKeyTest {
|
||||
|
||||
@Test
|
||||
public void testAttemptToDecryptWithRemovedPrivateKeysThrows()
|
||||
throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException {
|
||||
PGPSecretKeyRing secretKeys = PGPainless.generateKeyRing()
|
||||
.modernKeyRing("Hardy Hardware <hardy@hard.ware>");
|
||||
PGPPublicKeyRing certificate = PGPainless.extractCertificate(secretKeys);
|
||||
|
||||
ByteArrayOutputStream ciphertextOut = new ByteArrayOutputStream();
|
||||
EncryptionStream encryptionStream = PGPainless.encryptAndOrSign()
|
||||
.onOutputStream(ciphertextOut)
|
||||
.withOptions(
|
||||
ProducerOptions.encrypt(EncryptionOptions.get().addRecipient(certificate)));
|
||||
ByteArrayInputStream plaintextIn = new ByteArrayInputStream("Hello, World!\n".getBytes());
|
||||
Streams.pipeAll(plaintextIn, encryptionStream);
|
||||
encryptionStream.close();
|
||||
|
||||
PGPSecretKeyRing removedKeys = GnuPGDummyKeyUtil.modify(secretKeys)
|
||||
.removePrivateKeys(GnuPGDummyKeyUtil.KeyFilter.any());
|
||||
|
||||
ByteArrayInputStream ciphertextIn = new ByteArrayInputStream(ciphertextOut.toByteArray());
|
||||
assertThrows(MissingDecryptionMethodException.class, () -> PGPainless.decryptAndOrVerify()
|
||||
.onInputStream(ciphertextIn)
|
||||
.withOptions(ConsumerOptions.get().addDecryptionKey(removedKeys)));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,410 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.decryption_verification;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
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;
|
||||
|
||||
public class UnsupportedPacketVersionsTest {
|
||||
|
||||
private static final String KEY = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" +
|
||||
"Comment: Bob's OpenPGP Transferable Secret Key\n" +
|
||||
"\n" +
|
||||
"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\n" +
|
||||
"=miES\n" +
|
||||
"-----END PGP PRIVATE KEY BLOCK-----\n";
|
||||
|
||||
private static final String PKESK3_PKESK23_SEIP = "-----BEGIN PGP MESSAGE-----\n" +
|
||||
"\n" +
|
||||
"wcDMA3wvqk35PDeyAQv/YP+fDWtifT7KSk+tWrgbyvsYCt5Wh0IPESTZuiptwvto\n" +
|
||||
"CGbOfwuPbqqzzlFqSvX3UiJwhxjSSB3a1EBIOsbhc4grip/wm+fB50S/nTJxkJ14\n" +
|
||||
"qid40D7HOcIvuz1iQr1QoMNB0oT3nCwMec8mPUX2yOzx1eqr62SZUTCr6FdAmdYI\n" +
|
||||
"1u4EAeEFhRO0rcPRrpMZqwkXtUfx+pu7OzBS0qmOlfkQ50kbETDXBik4iXi30AGl\n" +
|
||||
"Ifo792oRo6DFK7ENquTNRqFPfezjrGZfkJrPWulWh28GogWTpOBwfXG8X262QoIp\n" +
|
||||
"VwZygi7wfj1jh2sXPvWgHjsjjTt7HPAiLI1f6IUl8WCQfPuQkFwCwPv63/rve59v\n" +
|
||||
"sBaeCEykAxdzMbP1oYSBBtONSAPYW9fsUsJSpuuLvxH252+luk09uQXWd6z4aCDm\n" +
|
||||
"EXiolhbkzL3mXCpVP6nMjRkm2ERE1yAWgXGT9JON0gcCb3eVqw6wzOYu+Vwq70ND\n" +
|
||||
"vKYlTMY+9RUx7wLn51UgwUoXQUFBQUFBQUEJYWFhYWFhYWFhYWFhYWFhYWFhYWFh\n" +
|
||||
"YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYdJMAQZo\n" +
|
||||
"tUFmYcTQrd5IFriHbF8h/Ov/xKlkW1QOPrZ+ziMQRbwyY4pVwNbZjCNVCHg5QDO4\n" +
|
||||
"wjF686DfZt83NvVYbJ7QNuENoI4YcFj8nw==\n" +
|
||||
"=VS1M\n" +
|
||||
"-----END PGP MESSAGE-----";
|
||||
|
||||
private static final String PKESK23_PKESK3_SEIP = "-----BEGIN PGP MESSAGE-----\n" +
|
||||
"\n" +
|
||||
"wUoXQUFBQUFBQUEJYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFh\n" +
|
||||
"YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYcHAzAN8L6pN+Tw3sgEL/2D/nw1r\n" +
|
||||
"Yn0+ykpPrVq4G8r7GAreVodCDxEk2boqbcL7aAhmzn8Lj26qs85Rakr191IicIcY\n" +
|
||||
"0kgd2tRASDrG4XOIK4qf8JvnwedEv50ycZCdeKoneNA+xznCL7s9YkK9UKDDQdKE\n" +
|
||||
"95wsDHnPJj1F9sjs8dXqq+tkmVEwq+hXQJnWCNbuBAHhBYUTtK3D0a6TGasJF7VH\n" +
|
||||
"8fqbuzswUtKpjpX5EOdJGxEw1wYpOIl4t9ABpSH6O/dqEaOgxSuxDarkzUahT33s\n" +
|
||||
"46xmX5Caz1rpVodvBqIFk6TgcH1xvF9utkKCKVcGcoIu8H49Y4drFz71oB47I407\n" +
|
||||
"exzwIiyNX+iFJfFgkHz7kJBcAsD7+t/673ufb7AWnghMpAMXczGz9aGEgQbTjUgD\n" +
|
||||
"2FvX7FLCUqbri78R9udvpbpNPbkF1nes+Ggg5hF4qJYW5My95lwqVT+pzI0ZJthE\n" +
|
||||
"RNcgFoFxk/STjdIHAm93lasOsMzmLvlcKu9DQ7ymJUzGPvUVMe8C5+dVINJMAQZo\n" +
|
||||
"tUFmYcTQrd5IFriHbF8h/Ov/xKlkW1QOPrZ+ziMQRbwyY4pVwNbZjCNVCHg5QDO4\n" +
|
||||
"wjF686DfZt83NvVYbJ7QNuENoI4YcFj8nw==\n" +
|
||||
"=EhNy\n" +
|
||||
"-----END PGP MESSAGE-----\n";
|
||||
|
||||
private static final String PKESK3_SKESK23_SEIP = "-----BEGIN PGP MESSAGE-----\n" +
|
||||
"\n" +
|
||||
"wcDMA3wvqk35PDeyAQv/YP+fDWtifT7KSk+tWrgbyvsYCt5Wh0IPESTZuiptwvto\n" +
|
||||
"CGbOfwuPbqqzzlFqSvX3UiJwhxjSSB3a1EBIOsbhc4grip/wm+fB50S/nTJxkJ14\n" +
|
||||
"qid40D7HOcIvuz1iQr1QoMNB0oT3nCwMec8mPUX2yOzx1eqr62SZUTCr6FdAmdYI\n" +
|
||||
"1u4EAeEFhRO0rcPRrpMZqwkXtUfx+pu7OzBS0qmOlfkQ50kbETDXBik4iXi30AGl\n" +
|
||||
"Ifo792oRo6DFK7ENquTNRqFPfezjrGZfkJrPWulWh28GogWTpOBwfXG8X262QoIp\n" +
|
||||
"VwZygi7wfj1jh2sXPvWgHjsjjTt7HPAiLI1f6IUl8WCQfPuQkFwCwPv63/rve59v\n" +
|
||||
"sBaeCEykAxdzMbP1oYSBBtONSAPYW9fsUsJSpuuLvxH252+luk09uQXWd6z4aCDm\n" +
|
||||
"EXiolhbkzL3mXCpVP6nMjRkm2ERE1yAWgXGT9JON0gcCb3eVqw6wzOYu+Vwq70ND\n" +
|
||||
"vKYlTMY+9RUx7wLn51Ugw00XCQMINQp7MFzAc6T/YWFhYWFhYWFhYWFhYWFhYWFh\n" +
|
||||
"YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYdJM\n" +
|
||||
"AQZotUFmYcTQrd5IFriHbF8h/Ov/xKlkW1QOPrZ+ziMQRbwyY4pVwNbZjCNVCHg5\n" +
|
||||
"QDO4wjF686DfZt83NvVYbJ7QNuENoI4YcFj8nw==\n" +
|
||||
"=pvWj\n" +
|
||||
"-----END PGP MESSAGE-----\n";
|
||||
|
||||
private static final String SKESK23_PKESK3_SEIP = "-----BEGIN PGP MESSAGE-----\n" +
|
||||
"\n" +
|
||||
"w00XCQMINQp7MFzAc6T/YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFh\n" +
|
||||
"YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYcHAzAN8L6pN+Tw3sgEL/2D/\n" +
|
||||
"nw1rYn0+ykpPrVq4G8r7GAreVodCDxEk2boqbcL7aAhmzn8Lj26qs85Rakr191Ii\n" +
|
||||
"cIcY0kgd2tRASDrG4XOIK4qf8JvnwedEv50ycZCdeKoneNA+xznCL7s9YkK9UKDD\n" +
|
||||
"QdKE95wsDHnPJj1F9sjs8dXqq+tkmVEwq+hXQJnWCNbuBAHhBYUTtK3D0a6TGasJ\n" +
|
||||
"F7VH8fqbuzswUtKpjpX5EOdJGxEw1wYpOIl4t9ABpSH6O/dqEaOgxSuxDarkzUah\n" +
|
||||
"T33s46xmX5Caz1rpVodvBqIFk6TgcH1xvF9utkKCKVcGcoIu8H49Y4drFz71oB47\n" +
|
||||
"I407exzwIiyNX+iFJfFgkHz7kJBcAsD7+t/673ufb7AWnghMpAMXczGz9aGEgQbT\n" +
|
||||
"jUgD2FvX7FLCUqbri78R9udvpbpNPbkF1nes+Ggg5hF4qJYW5My95lwqVT+pzI0Z\n" +
|
||||
"JthERNcgFoFxk/STjdIHAm93lasOsMzmLvlcKu9DQ7ymJUzGPvUVMe8C5+dVINJM\n" +
|
||||
"AQZotUFmYcTQrd5IFriHbF8h/Ov/xKlkW1QOPrZ+ziMQRbwyY4pVwNbZjCNVCHg5\n" +
|
||||
"QDO4wjF686DfZt83NvVYbJ7QNuENoI4YcFj8nw==\n" +
|
||||
"=STOd\n" +
|
||||
"-----END PGP MESSAGE-----\n";
|
||||
|
||||
private static final String PKESK3_SKESK4wS2K23_SEIP = "-----BEGIN PGP MESSAGE-----\n" +
|
||||
"\n" +
|
||||
"wcDMA3wvqk35PDeyAQv/YP+fDWtifT7KSk+tWrgbyvsYCt5Wh0IPESTZuiptwvto\n" +
|
||||
"CGbOfwuPbqqzzlFqSvX3UiJwhxjSSB3a1EBIOsbhc4grip/wm+fB50S/nTJxkJ14\n" +
|
||||
"qid40D7HOcIvuz1iQr1QoMNB0oT3nCwMec8mPUX2yOzx1eqr62SZUTCr6FdAmdYI\n" +
|
||||
"1u4EAeEFhRO0rcPRrpMZqwkXtUfx+pu7OzBS0qmOlfkQ50kbETDXBik4iXi30AGl\n" +
|
||||
"Ifo792oRo6DFK7ENquTNRqFPfezjrGZfkJrPWulWh28GogWTpOBwfXG8X262QoIp\n" +
|
||||
"VwZygi7wfj1jh2sXPvWgHjsjjTt7HPAiLI1f6IUl8WCQfPuQkFwCwPv63/rve59v\n" +
|
||||
"sBaeCEykAxdzMbP1oYSBBtONSAPYW9fsUsJSpuuLvxH252+luk09uQXWd6z4aCDm\n" +
|
||||
"EXiolhbkzL3mXCpVP6nMjRkm2ERE1yAWgXGT9JON0gcCb3eVqw6wzOYu+Vwq70ND\n" +
|
||||
"vKYlTMY+9RUx7wLn51Ugw1AECRcIYWFhYWFhYWFBQUFBYWFhYWFhYWFhYWFhYWFh\n" +
|
||||
"YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFh\n" +
|
||||
"YdJMAQZotUFmYcTQrd5IFriHbF8h/Ov/xKlkW1QOPrZ+ziMQRbwyY4pVwNbZjCNV\n" +
|
||||
"CHg5QDO4wjF686DfZt83NvVYbJ7QNuENoI4YcFj8nw==\n" +
|
||||
"=/uxY\n" +
|
||||
"-----END PGP MESSAGE-----\n";
|
||||
|
||||
private static final String SKESK4wS2K23_PKESK3_SEIP = "-----BEGIN PGP MESSAGE-----\n" +
|
||||
"\n" +
|
||||
"w1AECRcIYWFhYWFhYWFBQUFBYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFh\n" +
|
||||
"YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYcHAzAN8L6pN+Tw3sgEL\n" +
|
||||
"/2D/nw1rYn0+ykpPrVq4G8r7GAreVodCDxEk2boqbcL7aAhmzn8Lj26qs85Rakr1\n" +
|
||||
"91IicIcY0kgd2tRASDrG4XOIK4qf8JvnwedEv50ycZCdeKoneNA+xznCL7s9YkK9\n" +
|
||||
"UKDDQdKE95wsDHnPJj1F9sjs8dXqq+tkmVEwq+hXQJnWCNbuBAHhBYUTtK3D0a6T\n" +
|
||||
"GasJF7VH8fqbuzswUtKpjpX5EOdJGxEw1wYpOIl4t9ABpSH6O/dqEaOgxSuxDark\n" +
|
||||
"zUahT33s46xmX5Caz1rpVodvBqIFk6TgcH1xvF9utkKCKVcGcoIu8H49Y4drFz71\n" +
|
||||
"oB47I407exzwIiyNX+iFJfFgkHz7kJBcAsD7+t/673ufb7AWnghMpAMXczGz9aGE\n" +
|
||||
"gQbTjUgD2FvX7FLCUqbri78R9udvpbpNPbkF1nes+Ggg5hF4qJYW5My95lwqVT+p\n" +
|
||||
"zI0ZJthERNcgFoFxk/STjdIHAm93lasOsMzmLvlcKu9DQ7ymJUzGPvUVMe8C5+dV\n" +
|
||||
"INJMAQZotUFmYcTQrd5IFriHbF8h/Ov/xKlkW1QOPrZ+ziMQRbwyY4pVwNbZjCNV\n" +
|
||||
"CHg5QDO4wjF686DfZt83NvVYbJ7QNuENoI4YcFj8nw==\n" +
|
||||
"=cIwV\n" +
|
||||
"-----END PGP MESSAGE-----\n";
|
||||
|
||||
private static final String PKESK3_SEIP_OPS3_LIT_SIG4 = "-----BEGIN PGP MESSAGE-----\n" +
|
||||
"\n" +
|
||||
"wcDMA3wvqk35PDeyAQv+IhkCMhbdRcMnIPZNPGU6OK1Jk5xuRdIEIBsvv7b8jmAr\n" +
|
||||
"9IwjfnV/RDMtH+xR/T9K7qJGGFYnhLY5w0CmYHQcDKpcBqk0Dw6l/eKCNhgRXKAk\n" +
|
||||
"gfaKL1Utt1Pw0nz0mOwHyPEN/pGc0xlVhsjVkRvIOsKcpfuc1EpSZMFgDcBQDhe/\n" +
|
||||
"jAsR/MvRugkW8xLpyQyfeGLJUOEYVrkpam3rLKB1KywAgBmpr9WDwfYITW/VE9k5\n" +
|
||||
"cKIOPMDJFU+u9lzBx6nSS4JRBuCO2mhR4gjcRaGPWiiz+0qZfS+AXYV/MAU5OwK/\n" +
|
||||
"6nhX97zwS4r1Avztjh4taBhLVsY4pw6PuLtACJNwPrev63Yc+a4hJJ4pm1AnHV58\n" +
|
||||
"Y1pZKQL8vA61+/tbhFQ18vbJ8E1NOka/euFLQu9Mg58jhpcscqMouyr3JFMwgH2Y\n" +
|
||||
"eFuRJncJAKotXxlfnF37qz5LG3bamACXWZSObjp9d4quIAoCDUteZlDWQ1xq5R4y\n" +
|
||||
"QYXtk9ZuHsHsmY9A0CiI0sGYAUBtLubJ5qhLLr/GqKAmy8jTSA3MjgtrB55NWj9J\n" +
|
||||
"bjgFNsd1BNGklgnwhtmApHJWY8skAAQkJj0rXj/aOMc734ypiEWDiU1quRbEeRLR\n" +
|
||||
"kDvBNUXx2j2rVF+MmQS/sm5Yk/op+4lH/Wounsci3qWH76GaNZoIlvNE3mdFoVTe\n" +
|
||||
"cRh4W2Em8uAUH4bKwazltRJUhZmXvuGfUnQCmolJTpyPl4DaQQgzdBXLTRcPxwdU\n" +
|
||||
"30e7HnxZWESKx1LnGxp3Oan1k1lXyHwvnEk26EXhve+dhsQ6YsKgvtSLNFqGsfKe\n" +
|
||||
"MVOq37cpOGFQsYStWHZd0tcHjIjWmAeZ8kH+ZzR9tgYKxxjimsxafLS/lo415SkC\n" +
|
||||
"LnOCz6hywI7CufSUcXUlHGJuobZ5HDJcygsQhQmNVLDmKh4xUJDZrORS0ciMy2kc\n" +
|
||||
"XAnxCDYbltVQktc/F/Gl1lTx0UNmV5d9G0utVmxxGbXna3BLkB+6qMuux/ngC+cI\n" +
|
||||
"+0GjeuXDVzUqhKDDC3Sq4T2nwix4CjKvawnHC+vpHzRmdZSkXz3nrSdhJS5JnOap\n" +
|
||||
"I4CdyYkP3jQs5P03dkv5RZAoqNPkeftwadu0cjZj8HEC8bVSd7YCS/0Gbgvp/eK3\n" +
|
||||
"aOXZAWhELyQON4bbXiWzMTcO2AA5soBmP2QnBNdq7NxbEkBec8aAXZiyXn17JYit\n" +
|
||||
"HRtJ7beptXjN0y2bEhOvsBHbDpjGO22fZfQ0aywP2k4/XanDf4WJolEFgLj2Qp8F\n" +
|
||||
"pw1Olc2UhApxhAqjkme09hggli3wUhthQrpBlmntfbvBcmmO+p/jV8Zn\n" +
|
||||
"=Roko\n" +
|
||||
"-----END PGP MESSAGE-----\n";
|
||||
|
||||
private static final String PKESK3_SEIP_OPS23_LIT_SIG23 = "-----BEGIN PGP MESSAGE-----\n" +
|
||||
"\n" +
|
||||
"wcDMA3wvqk35PDeyAQv/byYGOehnfgZu/HJSSDEQhYE27lh5j+oQPktYBxsTI3Ii\n" +
|
||||
"AJ0pyJggtUM3c/e6z4HioAs4oQiaH1eoGBIl7noROhHJgT6E/oKdHJHmQndo0gT7\n" +
|
||||
"xzSHKrEZYEqT45LD6jT5teOiwGX/B7as6jaQOT+Nh+M0ZqpPrBdWDeUoY/I/lx9j\n" +
|
||||
"IcXQUhKuqJwZ16xsnv0JJ80rdp5qv0g2NHT1hK1JEONyT2fefov3EXaSpQZHRBEi\n" +
|
||||
"XfwToHcJrgemFoGwZBQhXsPWBgKH92aW6r7ZJZMQ4BE2SwqEw+cbaaqwfFRJ9puj\n" +
|
||||
"ZUBi2JGwnYImZyD7jYverkjH05vI7d5qQDhCT6GPD8Q0WKfY225LFTj/zzbC23lp\n" +
|
||||
"VLlbT2Ap8ZFOESpM+crOUaOguBhnTOF05s1eXhQYxIKzJTW8UVrzbwI8ut3BnEDj\n" +
|
||||
"0aDqUR+QDYgYmz8hnEjanHk2McvfaNdV6uOPZNph2RuPzhZeoNcz1PLy+XPFJsQT\n" +
|
||||
"EdqURJ2D35qmrBvq6klN0sGYAXEtW4hxMaKhH7/SIk2m3kUAChjtzr4XRcE2i8h3\n" +
|
||||
"9uD6CRWxokArfRVAp5RpXt3ywoYrl1Mp5prVwWcrTzYVPZwwe/bYAFTIfavi0Ezb\n" +
|
||||
"A/ah2e1EYCTWxLY0Klzil2Xw9/Dc/JPTRqzWJxIn0AU4DVfYNwlH3QimDUDKOKbu\n" +
|
||||
"bw4bLEEBKRr5BcNMA2rJOw/n3AmKxQcdSFJh3ZNtDPWzRwflIzE2qB686hpeOs34\n" +
|
||||
"T2iJcfr9W9rKcYI7+WYcZA3fWokaWUfXdWrPMXBVJuPdGezGLSfe/OM4kw/8s2vR\n" +
|
||||
"Bk88WZ1ZiFXE2CRaHP80fHFpxAZioWTuC5UGhF7NgZ7Q1E85GaGVe1fQeqmeX3mo\n" +
|
||||
"gwAWwq9WFhPQQPwdrDz+1h/pzD0RVW7D+zfWdF//vesc4z1Bpi5prbMdpVdmyvO9\n" +
|
||||
"8Rcc42GtmhtYSB9SPjzPuN8PrWvD3AKgw5vQro6oiNh0TGmj5Se4lXCfRfCl4tak\n" +
|
||||
"cmvdi+1wAvn5OFdxYKKHNvjavwj2SY70nx0ACasBpbMEwoQ0StZmOxCaofkgvEwN\n" +
|
||||
"t8jMq/MVrIfunhMjx0/GDpBZBb8kze8zrvWxlbTnoIfh2yVEqmTZWue9HX5Mnh8P\n" +
|
||||
"wexxNrPaafTjA3nUgXXzlItJ/Wa43pYw2sgcBPlF/jRKSFiD+pDLysqwpH0ANsJ3\n" +
|
||||
"G7t8Qavq1DlrHFgV5jhfR1tjA5ohjxx7yzceQBvZUFxKM1WEWR+9dRb3bpfZJr0g\n" +
|
||||
"qgO36bpCeCEej+ubXpXXTN28LQLXjQHlE2o1NGLoGl+G72tXOTx30kPS\n" +
|
||||
"=wUZn\n" +
|
||||
"-----END PGP MESSAGE-----\n";
|
||||
|
||||
private static final String PKESK3_SEIP_OPS23_OPS3_LIT_SIG4_SIG23 = "-----BEGIN PGP MESSAGE-----\n" +
|
||||
"\n" +
|
||||
"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\n" +
|
||||
"=qFHk\n" +
|
||||
"-----END PGP MESSAGE-----\n";
|
||||
|
||||
private static final String PKESK3_SEIP_OPS3_OPS23_LIT_SIG23_SIG4 = "-----BEGIN PGP MESSAGE-----\n" +
|
||||
"\n" +
|
||||
"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\n" +
|
||||
"=jGpv\n" +
|
||||
"-----END PGP MESSAGE-----\n";
|
||||
|
||||
private final PGPSecretKeyRing key;
|
||||
private final PGPPublicKeyRing cert;
|
||||
|
||||
public UnsupportedPacketVersionsTest() throws IOException {
|
||||
key = PGPainless.readKeyRing().secretKeyRing(KEY);
|
||||
cert = PGPainless.extractCertificate(key);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pkesk3_pkesk23_seip() throws PGPException, IOException {
|
||||
decryptAndCompare(PKESK3_PKESK23_SEIP, "Encrypted using SEIP + MDC.");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pkesk23_pkesk_seip() throws PGPException, IOException {
|
||||
decryptAndCompare(PKESK23_PKESK3_SEIP, "Encrypted using SEIP + MDC.");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pkesk3_skesk23_seip() throws PGPException, IOException {
|
||||
decryptAndCompare(PKESK3_SKESK23_SEIP, "Encrypted using SEIP + MDC.");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void skesk23_pkesk3_seip() throws PGPException, IOException {
|
||||
decryptAndCompare(SKESK23_PKESK3_SEIP, "Encrypted using SEIP + MDC.");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled("Enable once https://github.com/bcgit/bc-java/pull/1268 is available")
|
||||
public void pkesk3_skesk4Ws2k23_seip() throws PGPException, IOException {
|
||||
decryptAndCompare(PKESK3_SKESK4wS2K23_SEIP, "Encrypted using SEIP + MDC.");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled("Enable once https://github.com/bcgit/bc-java/pull/1268 is available")
|
||||
public void skesk4Ws2k23_pkesk3_seip() throws PGPException, IOException {
|
||||
decryptAndCompare(SKESK4wS2K23_PKESK3_SEIP, "Encrypted using SEIP + MDC.");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pkesk3_seip_ops3_lit_sig4() throws PGPException, IOException {
|
||||
decryptAndCompare(PKESK3_SEIP_OPS3_LIT_SIG4, "Encrypted, signed message.");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pkesk3_seip_ops23_lit_sig23() throws PGPException, IOException {
|
||||
decryptAndCompare(PKESK3_SEIP_OPS23_LIT_SIG23, "Encrypted, signed message.");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pkesk3_seip_ops23_ops3_lit_sig4_sig23() throws PGPException, IOException {
|
||||
decryptAndCompare(PKESK3_SEIP_OPS23_OPS3_LIT_SIG4_SIG23, "Encrypted, signed message.");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pkesk3_seip_ops3_ops23_lit_sig23_sig4() throws PGPException, IOException {
|
||||
decryptAndCompare(PKESK3_SEIP_OPS3_OPS23_LIT_SIG23_SIG4, "Encrypted, signed message.");
|
||||
}
|
||||
|
||||
public void decryptAndCompare(String msg, String plain) throws IOException, PGPException {
|
||||
// noinspection CharsetObjectCanBeUsed
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(msg.getBytes(Charset.forName("UTF8")));
|
||||
DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify()
|
||||
.onInputStream(inputStream)
|
||||
.withOptions(ConsumerOptions.get()
|
||||
.addDecryptionKey(key)
|
||||
.addVerificationCert(cert));
|
||||
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
Streams.pipeAll(decryptionStream, out);
|
||||
decryptionStream.close();
|
||||
|
||||
assertEquals(plain, out.toString());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,197 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.decryption_verification.syntax_check;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.pgpainless.exception.MalformedOpenPgpMessageException;
|
||||
|
||||
public class PDATest {
|
||||
|
||||
|
||||
/**
|
||||
* MSG is valid.
|
||||
*
|
||||
* @throws MalformedOpenPgpMessageException fail
|
||||
*/
|
||||
@Test
|
||||
public void testSimpleLiteralMessageIsValid() throws MalformedOpenPgpMessageException {
|
||||
PDA check = new PDA();
|
||||
check.next(InputSymbol.LiteralData);
|
||||
check.next(InputSymbol.EndOfSequence);
|
||||
|
||||
assertTrue(check.isValid());
|
||||
}
|
||||
|
||||
/**
|
||||
* OPS MSG SIG is valid.
|
||||
*
|
||||
* @throws MalformedOpenPgpMessageException fail
|
||||
*/
|
||||
@Test
|
||||
public void testSimpleOpsSignedMesssageIsValid() throws MalformedOpenPgpMessageException {
|
||||
PDA check = new PDA();
|
||||
check.next(InputSymbol.OnePassSignature);
|
||||
check.next(InputSymbol.LiteralData);
|
||||
check.next(InputSymbol.Signature);
|
||||
check.next(InputSymbol.EndOfSequence);
|
||||
|
||||
assertTrue(check.isValid());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* SIG MSG is valid.
|
||||
*
|
||||
* @throws MalformedOpenPgpMessageException fail
|
||||
*/
|
||||
@Test
|
||||
public void testSimplePrependSignedMessageIsValid() throws MalformedOpenPgpMessageException {
|
||||
PDA check = new PDA();
|
||||
check.next(InputSymbol.Signature);
|
||||
check.next(InputSymbol.LiteralData);
|
||||
check.next(InputSymbol.EndOfSequence);
|
||||
|
||||
assertTrue(check.isValid());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* OPS COMP(MSG) SIG is valid.
|
||||
*
|
||||
* @throws MalformedOpenPgpMessageException fail
|
||||
*/
|
||||
@Test
|
||||
public void testOPSSignedCompressedMessageIsValid() throws MalformedOpenPgpMessageException {
|
||||
PDA check = new PDA();
|
||||
check.next(InputSymbol.OnePassSignature);
|
||||
check.next(InputSymbol.CompressedData);
|
||||
// Here would be a nested PDA for the LiteralData packet
|
||||
check.next(InputSymbol.Signature);
|
||||
check.next(InputSymbol.EndOfSequence);
|
||||
|
||||
assertTrue(check.isValid());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOPSSignedEncryptedMessageIsValid() {
|
||||
PDA check = new PDA();
|
||||
check.next(InputSymbol.OnePassSignature);
|
||||
check.next(InputSymbol.EncryptedData);
|
||||
check.next(InputSymbol.Signature);
|
||||
check.next(InputSymbol.EndOfSequence);
|
||||
assertTrue(check.isValid());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void anyInputAfterEOSIsNotValid() {
|
||||
PDA check = new PDA();
|
||||
check.next(InputSymbol.LiteralData);
|
||||
check.next(InputSymbol.EndOfSequence);
|
||||
assertThrows(MalformedOpenPgpMessageException.class,
|
||||
() -> check.next(InputSymbol.Signature));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncryptedMessageWithAppendedStandaloneSigIsNotValid() {
|
||||
PDA check = new PDA();
|
||||
check.next(InputSymbol.EncryptedData);
|
||||
assertThrows(MalformedOpenPgpMessageException.class,
|
||||
() -> check.next(InputSymbol.Signature));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOPSSignedEncryptedMessageWithMissingSigIsNotValid() {
|
||||
PDA check = new PDA();
|
||||
check.next(InputSymbol.OnePassSignature);
|
||||
check.next(InputSymbol.EncryptedData);
|
||||
assertThrows(MalformedOpenPgpMessageException.class,
|
||||
() -> check.next(InputSymbol.EndOfSequence));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTwoLiteralDataIsNotValid() {
|
||||
PDA check = new PDA();
|
||||
check.next(InputSymbol.LiteralData);
|
||||
assertThrows(MalformedOpenPgpMessageException.class,
|
||||
() -> check.next(InputSymbol.LiteralData));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTrailingSigIsNotValid() {
|
||||
PDA check = new PDA();
|
||||
check.next(InputSymbol.LiteralData);
|
||||
assertThrows(MalformedOpenPgpMessageException.class,
|
||||
() -> check.next(InputSymbol.Signature));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOPSAloneIsNotValid() {
|
||||
PDA check = new PDA();
|
||||
check.next(InputSymbol.OnePassSignature);
|
||||
assertThrows(MalformedOpenPgpMessageException.class,
|
||||
() -> check.next(InputSymbol.EndOfSequence));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOPSLitWithMissingSigIsNotValid() {
|
||||
PDA check = new PDA();
|
||||
check.next(InputSymbol.OnePassSignature);
|
||||
check.next(InputSymbol.LiteralData);
|
||||
assertThrows(MalformedOpenPgpMessageException.class,
|
||||
() -> check.next(InputSymbol.EndOfSequence));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompressedMessageWithStandalongAppendedSigIsNotValid() {
|
||||
PDA check = new PDA();
|
||||
check.next(InputSymbol.CompressedData);
|
||||
assertThrows(MalformedOpenPgpMessageException.class,
|
||||
() -> check.next(InputSymbol.Signature));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOPSCompressedDataWithMissingSigIsNotValid() {
|
||||
PDA check = new PDA();
|
||||
check.next(InputSymbol.OnePassSignature);
|
||||
check.next(InputSymbol.CompressedData);
|
||||
assertThrows(MalformedOpenPgpMessageException.class,
|
||||
() -> check.next(InputSymbol.EndOfSequence));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompressedMessageFollowedByTrailingLiteralDataIsNotValid() {
|
||||
PDA check = new PDA();
|
||||
check.next(InputSymbol.CompressedData);
|
||||
assertThrows(MalformedOpenPgpMessageException.class,
|
||||
() -> check.next(InputSymbol.LiteralData));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOPSWithPrependedSigIsValid() {
|
||||
PDA check = new PDA();
|
||||
check.next(InputSymbol.Signature);
|
||||
check.next(InputSymbol.OnePassSignature);
|
||||
check.next(InputSymbol.LiteralData);
|
||||
check.next(InputSymbol.Signature);
|
||||
check.next(InputSymbol.EndOfSequence);
|
||||
|
||||
assertTrue(check.isValid());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPrependedSigInsideOPSSignedMessageIsValid() {
|
||||
PDA check = new PDA();
|
||||
check.next(InputSymbol.OnePassSignature);
|
||||
check.next(InputSymbol.Signature);
|
||||
check.next(InputSymbol.LiteralData);
|
||||
check.next(InputSymbol.Signature);
|
||||
check.next(InputSymbol.EndOfSequence);
|
||||
|
||||
assertTrue(check.isValid());
|
||||
}
|
||||
}
|
|
@ -9,7 +9,6 @@ import static org.junit.jupiter.api.Assertions.assertNull;
|
|||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
|
@ -165,7 +164,7 @@ public class EncryptionOptionsTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testAddRecipients_PGPPublicKeyRingCollection() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException {
|
||||
public void testAddRecipients_PGPPublicKeyRingCollection() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException {
|
||||
PGPPublicKeyRing secondKeyRing = KeyRingUtils.publicKeyRingFrom(
|
||||
PGPainless.generateKeyRing().modernKeyRing("other@pgpainless.org"));
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ import org.pgpainless.PGPainless;
|
|||
* This class contains a set of slightly out of spec or weird keys.
|
||||
* Those are used to test whether implementations behave correctly when dealing with such keys.
|
||||
*
|
||||
* Original source: https://gitlab.com/sequoia-pgp/weird-keys
|
||||
* @see <a href="https://gitlab.com/sequoia-pgp/weird-keys">Original Source</a>
|
||||
*/
|
||||
public class WeirdKeys {
|
||||
|
||||
|
|
|
@ -140,7 +140,7 @@ public class CertifyCertificateTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testScopedDelegation() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException {
|
||||
public void testScopedDelegation() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException {
|
||||
PGPSecretKeyRing aliceKey = PGPainless.generateKeyRing()
|
||||
.modernKeyRing("Alice <alice@pgpainless.org>");
|
||||
PGPSecretKeyRing caKey = PGPainless.generateKeyRing()
|
||||
|
|
|
@ -7,7 +7,6 @@ package org.pgpainless.key.generation;
|
|||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Date;
|
||||
|
@ -26,14 +25,15 @@ import org.pgpainless.key.generation.type.rsa.RsaLength;
|
|||
import org.pgpainless.key.util.KeyRingUtils;
|
||||
import org.pgpainless.key.util.UserId;
|
||||
import org.pgpainless.timeframe.TestTimeFrameProvider;
|
||||
import org.pgpainless.util.DateUtil;
|
||||
import org.pgpainless.util.TestAllImplementations;
|
||||
|
||||
public class GenerateKeyWithAdditionalUserIdTest {
|
||||
|
||||
@TestTemplate
|
||||
@ExtendWith(TestAllImplementations.class)
|
||||
public void test() throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException, IOException {
|
||||
Date now = new Date();
|
||||
public void test() throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException {
|
||||
Date now = DateUtil.now();
|
||||
Date expiration = TestTimeFrameProvider.defaultExpirationForCreationDate(now);
|
||||
PGPSecretKeyRing secretKeys = PGPainless.buildKeyRing()
|
||||
.setPrimaryKey(KeySpec.getBuilder(
|
||||
|
|
|
@ -20,7 +20,7 @@ import org.pgpainless.util.TestAllImplementations;
|
|||
import org.pgpainless.util.Passphrase;
|
||||
|
||||
/**
|
||||
* Reproduce behavior of https://github.com/pgpainless/pgpainless/issues/16
|
||||
* Reproduce behavior of <a href="https://github.com/pgpainless/pgpainless/issues/16>this issue</a>
|
||||
* and verify that the fix is working.
|
||||
*
|
||||
* The issue is that the implementation of {@link Passphrase#emptyPassphrase()} would set the underlying
|
||||
|
|
|
@ -20,6 +20,7 @@ import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
|||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSignature;
|
||||
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.decryption_verification.ConsumerOptions;
|
||||
|
@ -159,6 +160,8 @@ public class IgnoreMarkerPackets {
|
|||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
// TODO: Enable and fix
|
||||
public void markerPlusEncryptedMessage() throws IOException, PGPException {
|
||||
String msg = "-----BEGIN PGP MESSAGE-----\n" +
|
||||
"\n" +
|
||||
|
|
|
@ -8,7 +8,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
|||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Iterator;
|
||||
|
@ -19,6 +18,7 @@ import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
|||
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
|
||||
import org.bouncycastle.util.Arrays;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.algorithm.KeyFlag;
|
||||
|
@ -35,8 +35,7 @@ public class BCUtilTest {
|
|||
|
||||
@Test
|
||||
public void keyRingToCollectionTest()
|
||||
throws PGPException, NoSuchAlgorithmException, InvalidAlgorithmParameterException,
|
||||
IOException {
|
||||
throws PGPException, NoSuchAlgorithmException, InvalidAlgorithmParameterException {
|
||||
PGPSecretKeyRing sec = PGPainless.buildKeyRing()
|
||||
.setPrimaryKey(KeySpec.getBuilder(
|
||||
KeyType.RSA(RsaLength._3072),
|
||||
|
@ -94,14 +93,14 @@ public class BCUtilTest {
|
|||
@Test
|
||||
public void constantTimeAreEqualsTest() {
|
||||
char[] b = "Hello".toCharArray();
|
||||
assertTrue(BCUtil.constantTimeAreEqual(b, b));
|
||||
assertTrue(BCUtil.constantTimeAreEqual("Hello".toCharArray(), "Hello".toCharArray()));
|
||||
assertTrue(BCUtil.constantTimeAreEqual(new char[0], new char[0]));
|
||||
assertTrue(BCUtil.constantTimeAreEqual(new char[] {'H', 'e', 'l', 'l', 'o'}, "Hello".toCharArray()));
|
||||
assertTrue(Arrays.constantTimeAreEqual(b, b));
|
||||
assertTrue(Arrays.constantTimeAreEqual("Hello".toCharArray(), "Hello".toCharArray()));
|
||||
assertTrue(Arrays.constantTimeAreEqual(new char[0], new char[0]));
|
||||
assertTrue(Arrays.constantTimeAreEqual(new char[] {'H', 'e', 'l', 'l', 'o'}, "Hello".toCharArray()));
|
||||
|
||||
assertFalse(BCUtil.constantTimeAreEqual("Hello".toCharArray(), "Hello World".toCharArray()));
|
||||
assertFalse(BCUtil.constantTimeAreEqual(null, "Hello".toCharArray()));
|
||||
assertFalse(BCUtil.constantTimeAreEqual("Hello".toCharArray(), null));
|
||||
assertFalse(BCUtil.constantTimeAreEqual(null, null));
|
||||
assertFalse(Arrays.constantTimeAreEqual("Hello".toCharArray(), "Hello World".toCharArray()));
|
||||
assertFalse(Arrays.constantTimeAreEqual(null, "Hello".toCharArray()));
|
||||
assertFalse(Arrays.constantTimeAreEqual("Hello".toCharArray(), null));
|
||||
assertFalse(Arrays.constantTimeAreEqual((char[]) null, null));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,7 +56,7 @@ public class KeyRingsFromCollectionTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void selectPublicKeyRingFromPublicKeyRingCollectionTest() throws IOException, PGPException {
|
||||
public void selectPublicKeyRingFromPublicKeyRingCollectionTest() throws IOException {
|
||||
PGPPublicKeyRing emil = TestKeys.getEmilPublicKeyRing();
|
||||
PGPPublicKeyRing juliet = TestKeys.getJulietPublicKeyRing();
|
||||
PGPPublicKeyRingCollection collection = new PGPPublicKeyRingCollection(Arrays.asList(emil, juliet));
|
||||
|
@ -68,7 +68,7 @@ public class KeyRingsFromCollectionTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void selectPublicKeyRingMapFromPublicKeyRingCollectionMapTest() throws IOException, PGPException {
|
||||
public void selectPublicKeyRingMapFromPublicKeyRingCollectionMapTest() throws IOException {
|
||||
PGPPublicKeyRing emil = TestKeys.getEmilPublicKeyRing();
|
||||
PGPPublicKeyRing juliet = TestKeys.getJulietPublicKeyRing();
|
||||
MultiMap<String, PGPPublicKeyRingCollection> map = new MultiMap<>();
|
||||
|
|
|
@ -19,7 +19,7 @@ SPDX-License-Identifier: Apache-2.0
|
|||
</encoder>
|
||||
</appender>
|
||||
|
||||
<logger name="org.pgpainless" level="warn" additivity="false">
|
||||
<logger name="org.pgpainless" level="debug" additivity="false">
|
||||
<appender-ref ref="STDOUT" />
|
||||
</logger>
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ To start using pgpainless-sop in your code, include the following lines in your
|
|||
...
|
||||
dependencies {
|
||||
...
|
||||
implementation "org.pgpainless:pgpainless-sop:1.3.8"
|
||||
implementation "org.pgpainless:pgpainless-sop:1.4.0-rc1"
|
||||
...
|
||||
}
|
||||
|
||||
|
@ -34,7 +34,7 @@ dependencies {
|
|||
<dependency>
|
||||
<groupId>org.pgpainless</groupId>
|
||||
<artifactId>pgpainless-sop</artifactId>
|
||||
<version>1.3.8</version>
|
||||
<version>1.4.0-rc1</version>
|
||||
</dependency>
|
||||
...
|
||||
</dependencies>
|
||||
|
|
|
@ -18,7 +18,7 @@ import sop.operation.Dearmor;
|
|||
public class DearmorImpl implements Dearmor {
|
||||
|
||||
@Override
|
||||
public Ready data(InputStream data) throws IOException {
|
||||
public Ready data(InputStream data) {
|
||||
InputStream decoder;
|
||||
try {
|
||||
decoder = PGPUtil.getDecoderStream(data);
|
||||
|
|
|
@ -23,6 +23,7 @@ import org.pgpainless.decryption_verification.ConsumerOptions;
|
|||
import org.pgpainless.decryption_verification.DecryptionStream;
|
||||
import org.pgpainless.decryption_verification.OpenPgpMetadata;
|
||||
import org.pgpainless.decryption_verification.SignatureVerification;
|
||||
import org.pgpainless.exception.MalformedOpenPgpMessageException;
|
||||
import org.pgpainless.exception.MissingDecryptionMethodException;
|
||||
import org.pgpainless.exception.WrongPassphraseException;
|
||||
import org.pgpainless.util.Passphrase;
|
||||
|
@ -135,7 +136,7 @@ public class DecryptImpl implements Decrypt {
|
|||
throw new SOPGPException.CannotDecrypt();
|
||||
} catch (WrongPassphraseException e) {
|
||||
throw new SOPGPException.KeyIsProtected();
|
||||
} catch (PGPException | IOException e) {
|
||||
} catch (MalformedOpenPgpMessageException | PGPException | IOException e) {
|
||||
throw new SOPGPException.BadData(e);
|
||||
} finally {
|
||||
// Forget passphrases after decryption
|
||||
|
|
|
@ -18,6 +18,7 @@ import org.pgpainless.decryption_verification.ConsumerOptions;
|
|||
import org.pgpainless.decryption_verification.DecryptionStream;
|
||||
import org.pgpainless.decryption_verification.OpenPgpMetadata;
|
||||
import org.pgpainless.decryption_verification.SignatureVerification;
|
||||
import org.pgpainless.exception.MalformedOpenPgpMessageException;
|
||||
import sop.Verification;
|
||||
import sop.exception.SOPGPException;
|
||||
import sop.operation.DetachedVerify;
|
||||
|
@ -85,7 +86,7 @@ public class DetachedVerifyImpl implements DetachedVerify {
|
|||
}
|
||||
|
||||
return verificationList;
|
||||
} catch (PGPException e) {
|
||||
} catch (MalformedOpenPgpMessageException | PGPException e) {
|
||||
throw new SOPGPException.BadData(e);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -76,7 +76,7 @@ public class InlineSignImpl implements InlineSign {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Ready data(InputStream data) throws SOPGPException.KeyIsProtected, IOException, SOPGPException.ExpectedText {
|
||||
public Ready data(InputStream data) throws SOPGPException.KeyIsProtected, SOPGPException.ExpectedText {
|
||||
for (PGPSecretKeyRing key : signingKeys) {
|
||||
try {
|
||||
if (mode == InlineSignAs.CleartextSigned) {
|
||||
|
|
|
@ -19,6 +19,7 @@ import org.pgpainless.decryption_verification.ConsumerOptions;
|
|||
import org.pgpainless.decryption_verification.DecryptionStream;
|
||||
import org.pgpainless.decryption_verification.OpenPgpMetadata;
|
||||
import org.pgpainless.decryption_verification.SignatureVerification;
|
||||
import org.pgpainless.exception.MalformedOpenPgpMessageException;
|
||||
import sop.ReadyWithResult;
|
||||
import sop.Verification;
|
||||
import sop.exception.SOPGPException;
|
||||
|
@ -53,7 +54,7 @@ public class InlineVerifyImpl implements InlineVerify {
|
|||
}
|
||||
|
||||
@Override
|
||||
public ReadyWithResult<List<Verification>> data(InputStream data) throws IOException, SOPGPException.NoSignature, SOPGPException.BadData {
|
||||
public ReadyWithResult<List<Verification>> data(InputStream data) throws SOPGPException.NoSignature, SOPGPException.BadData {
|
||||
return new ReadyWithResult<List<Verification>>() {
|
||||
@Override
|
||||
public List<Verification> writeTo(OutputStream outputStream) throws IOException, SOPGPException.NoSignature {
|
||||
|
@ -84,7 +85,7 @@ public class InlineVerifyImpl implements InlineVerify {
|
|||
}
|
||||
|
||||
return verificationList;
|
||||
} catch (PGPException e) {
|
||||
} catch (MalformedOpenPgpMessageException | PGPException e) {
|
||||
throw new SOPGPException.BadData(e);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
|
||||
allprojects {
|
||||
ext {
|
||||
shortVersion = '1.3.9'
|
||||
isSnapshot = true
|
||||
shortVersion = '1.4.0-rc1'
|
||||
isSnapshot = false
|
||||
pgpainlessMinAndroidSdk = 10
|
||||
javaSourceCompatibility = 1.8
|
||||
bouncyCastleVersion = '1.72'
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue