1
0
Fork 0
mirror of https://github.com/pgpainless/pgpainless.git synced 2025-09-10 18:59:39 +02:00

Reworking encryption/decryption API.

This commit is contained in:
Paul Schaub 2021-05-06 00:04:03 +02:00
parent 7e2c89b1b3
commit 89a0adddd8
29 changed files with 1454 additions and 631 deletions

View file

@ -0,0 +1,34 @@
/*
* Copyright 2021 Paul Schaub.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.pgpainless.algorithm;
public enum DocumentSignatureType {
BINARY_DOCUMENT(SignatureType.BINARY_DOCUMENT),
CANONICAL_TEXT_DOCUMENT(SignatureType.CANONICAL_TEXT_DOCUMENT),
;
final SignatureType signatureType;
DocumentSignatureType(SignatureType signatureType) {
this.signatureType = signatureType;
}
public SignatureType getSignatureType() {
return signatureType;
}
}

View file

@ -17,311 +17,202 @@ package org.pgpainless.encryption_signing;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import javax.annotation.Nonnull;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
import org.pgpainless.PGPainless;
import org.pgpainless.algorithm.CompressionAlgorithm;
import org.pgpainless.algorithm.DocumentSignatureType;
import org.pgpainless.algorithm.HashAlgorithm;
import org.pgpainless.algorithm.KeyFlag;
import org.pgpainless.algorithm.SignatureType;
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
import org.pgpainless.decryption_verification.OpenPgpMetadata;
import org.pgpainless.key.KeyRingValidator;
import org.pgpainless.key.OpenPgpV4Fingerprint;
import org.pgpainless.key.SubkeyIdentifier;
import org.pgpainless.exception.KeyValidationException;
import org.pgpainless.key.protection.SecretKeyRingProtector;
import org.pgpainless.key.protection.UnlockSecretKey;
import org.pgpainless.policy.Policy;
import org.pgpainless.util.Passphrase;
import org.pgpainless.util.Tuple;
import org.pgpainless.util.selection.key.PublicKeySelectionStrategy;
import org.pgpainless.util.selection.key.SecretKeySelectionStrategy;
import org.pgpainless.util.selection.key.impl.And;
import org.pgpainless.util.selection.key.impl.EncryptionKeySelectionStrategy;
import org.pgpainless.util.selection.key.impl.NoRevocation;
import org.pgpainless.util.selection.key.impl.SignatureKeySelectionStrategy;
public class EncryptionBuilder implements EncryptionBuilderInterface {
private final EncryptionStream.Purpose purpose;
private OutputStream outputStream;
private final Map<SubkeyIdentifier, PGPPublicKeyRing> encryptionKeys = new ConcurrentHashMap<>();
private final Set<Passphrase> encryptionPassphrases = new HashSet<>();
private boolean detachedSignature = false;
private SignatureType signatureType = SignatureType.BINARY_DOCUMENT;
private final Map<SubkeyIdentifier, PGPSecretKeyRing> signingKeys = new ConcurrentHashMap<>();
private SecretKeyRingProtector signingKeysDecryptor;
private SymmetricKeyAlgorithm symmetricKeyAlgorithm = SymmetricKeyAlgorithm.AES_128;
private HashAlgorithm hashAlgorithm = HashAlgorithm.SHA256;
private CompressionAlgorithm compressionAlgorithm = CompressionAlgorithm.UNCOMPRESSED;
private boolean asciiArmor = false;
private EncryptionOptions encryptionOptions;
private SigningOptions signingOptions = new SigningOptions();
private ProducerOptions options;
private OpenPgpMetadata.FileInfo fileInfo;
public EncryptionBuilder() {
this.purpose = EncryptionStream.Purpose.COMMUNICATIONS;
this.encryptionOptions = new EncryptionOptions(EncryptionStream.Purpose.COMMUNICATIONS);
}
public EncryptionBuilder(@Nonnull EncryptionStream.Purpose purpose) {
this.purpose = purpose;
this.encryptionOptions = new EncryptionOptions(purpose);
}
@Override
public ToRecipients onOutputStream(@Nonnull OutputStream outputStream, OpenPgpMetadata.FileInfo fileInfo) {
public ToRecipientsOrNoEncryption onOutputStream(@Nonnull OutputStream outputStream, OpenPgpMetadata.FileInfo fileInfo) {
this.outputStream = outputStream;
this.fileInfo = fileInfo;
return new ToRecipientsImpl();
return new ToRecipientsOrNoEncryptionImpl();
}
class ToRecipientsImpl implements ToRecipients {
@Override
public WithAlgorithms toRecipients(@Nonnull PGPPublicKeyRing... keys) {
if (keys.length == 0) {
throw new IllegalArgumentException("No public keys provided.");
}
public AdditionalRecipients toRecipient(@Nonnull PGPPublicKeyRing key) {
encryptionOptions.addRecipient(key);
return new AdditionalRecipientsImpl();
}
Map<SubkeyIdentifier, PGPPublicKeyRing> encryptionKeys = new ConcurrentHashMap<>();
@Override
public AdditionalRecipients toRecipient(@Nonnull PGPPublicKeyRing key, @Nonnull String userId) {
encryptionOptions.addRecipient(key, userId);
return new AdditionalRecipientsImpl();
}
@Override
public AdditionalRecipients toRecipient(@Nonnull PGPPublicKeyRingCollection keys, @Nonnull String userId) {
for (PGPPublicKeyRing ring : keys) {
PGPPublicKeyRing validatedKeyRing = KeyRingValidator.validate(ring, PGPainless.getPolicy());
for (PGPPublicKey k : validatedKeyRing) {
if (encryptionKeySelector().accept(k)) {
encryptionKeys.put(new SubkeyIdentifier(ring, k.getKeyID()), ring);
}
}
encryptionOptions.addRecipient(ring, userId);
}
if (encryptionKeys.isEmpty()) {
throw new IllegalArgumentException("No valid encryption keys found!");
}
EncryptionBuilder.this.encryptionKeys.putAll(encryptionKeys);
return new WithAlgorithmsImpl();
}
private String getPrimaryUserId(PGPPublicKey publicKey) {
// TODO: Use real function to get primary userId.
return publicKey.getUserIDs().next();
return new AdditionalRecipientsImpl();
}
@Override
public WithAlgorithms toRecipients(@Nonnull PGPPublicKeyRingCollection... keys) {
if (keys.length == 0) {
throw new IllegalArgumentException("No key ring collections provided.");
public AdditionalRecipients toRecipients(@Nonnull PGPPublicKeyRingCollection keys) {
for (PGPPublicKeyRing ring : keys) {
encryptionOptions.addRecipient(ring);
}
for (PGPPublicKeyRingCollection collection : keys) {
for (PGPPublicKeyRing ring : collection) {
Map<SubkeyIdentifier, PGPPublicKeyRing> encryptionKeys = new ConcurrentHashMap<>();
for (PGPPublicKey k : ring) {
if (encryptionKeySelector().accept(k)) {
encryptionKeys.put(new SubkeyIdentifier(ring, k.getKeyID()), ring);
}
}
if (encryptionKeys.isEmpty()) {
throw new IllegalArgumentException("No valid encryption keys found!");
}
EncryptionBuilder.this.encryptionKeys.putAll(encryptionKeys);
}
}
return new WithAlgorithmsImpl();
return new AdditionalRecipientsImpl();
}
@Override
public WithAlgorithms forPassphrases(Passphrase... passphrases) {
List<Passphrase> passphraseList = new ArrayList<>();
for (Passphrase passphrase : passphrases) {
if (passphrase.isEmpty()) {
throw new IllegalArgumentException("Passphrase must not be empty.");
}
passphraseList.add(passphrase);
}
EncryptionBuilder.this.encryptionPassphrases.addAll(passphraseList);
return new WithAlgorithmsImpl();
}
@Override
public DetachedSign doNotEncrypt() {
return new DetachedSignImpl();
public AdditionalRecipients forPassphrase(Passphrase passphrase) {
encryptionOptions.addPassphrase(passphrase);
return new AdditionalRecipientsImpl();
}
}
class WithAlgorithmsImpl implements WithAlgorithms {
class ToRecipientsOrNoEncryptionImpl extends ToRecipientsImpl implements ToRecipientsOrNoEncryption {
@Override
public WithAlgorithms andToSelf(@Nonnull PGPPublicKeyRing... keys) {
if (keys.length == 0) {
throw new IllegalArgumentException("Recipient list MUST NOT be empty.");
public EncryptionStream withOptions(ProducerOptions options) throws PGPException, IOException {
if (options == null) {
throw new NullPointerException("ProducerOptions cannot be null.");
}
for (PGPPublicKeyRing ring : keys) {
Map<SubkeyIdentifier, PGPPublicKeyRing> encryptionKeys = new ConcurrentHashMap<>();
for (Iterator<PGPPublicKey> i = ring.getPublicKeys(); i.hasNext(); ) {
PGPPublicKey key = i.next();
if (encryptionKeySelector().accept(key)) {
encryptionKeys.put(new SubkeyIdentifier(ring, key.getKeyID()), ring);
}
}
if (encryptionKeys.isEmpty()) {
throw new IllegalArgumentException("No suitable encryption key found in the key ring " + new OpenPgpV4Fingerprint(ring));
}
EncryptionBuilder.this.encryptionKeys.putAll(encryptionKeys);
}
return this;
return new EncryptionStream(outputStream, options, fileInfo);
}
@Override
public WithAlgorithms andToSelf(@Nonnull PGPPublicKeyRingCollection keys) {
for (PGPPublicKeyRing ring : keys) {
Map<SubkeyIdentifier, PGPPublicKeyRing> encryptionKeys = new ConcurrentHashMap<>();
for (Iterator<PGPPublicKey> i = ring.getPublicKeys(); i.hasNext(); ) {
PGPPublicKey key = i.next();
if (encryptionKeySelector().accept(key)) {
encryptionKeys.put(new SubkeyIdentifier(ring, key.getKeyID()), ring);
}
}
if (encryptionKeys.isEmpty()) {
throw new IllegalArgumentException("No suitable encryption key found in the key ring " + new OpenPgpV4Fingerprint(ring));
}
EncryptionBuilder.this.encryptionKeys.putAll(encryptionKeys);
}
return this;
}
@Override
public DetachedSign usingAlgorithms(@Nonnull SymmetricKeyAlgorithm symmetricKeyAlgorithm,
@Nonnull HashAlgorithm hashAlgorithm,
@Nonnull CompressionAlgorithm compressionAlgorithm) {
EncryptionBuilder.this.symmetricKeyAlgorithm = symmetricKeyAlgorithm;
EncryptionBuilder.this.hashAlgorithm = hashAlgorithm;
EncryptionBuilder.this.compressionAlgorithm = compressionAlgorithm;
return new DetachedSignImpl();
}
@Override
public DetachedSign usingSecureAlgorithms() {
EncryptionBuilder.this.symmetricKeyAlgorithm = SymmetricKeyAlgorithm.AES_256;
EncryptionBuilder.this.hashAlgorithm = HashAlgorithm.SHA512;
EncryptionBuilder.this.compressionAlgorithm = CompressionAlgorithm.UNCOMPRESSED;
return new DetachedSignImpl();
}
@Override
public ToRecipients and() {
return new ToRecipientsImpl();
public SignWithOrDontSign doNotEncrypt() {
EncryptionBuilder.this.encryptionOptions = null;
return new SignWithOrDontSignImpl();
}
}
class DetachedSignImpl implements DetachedSign {
class AdditionalRecipientsImpl implements AdditionalRecipients {
@Override
public ToRecipientsOrSign and() {
return new ToRecipientsOrSignImpl();
}
}
class ToRecipientsOrSignImpl extends ToRecipientsImpl implements ToRecipientsOrSign {
@Override
public SignWith createDetachedSignature() {
EncryptionBuilder.this.detachedSignature = true;
return new SignWithImpl();
public Armor doNotSign() {
EncryptionBuilder.this.signingOptions = null;
return new ArmorImpl();
}
@Override
public AdditionalSignWith signWith(@Nonnull SecretKeyRingProtector decryptor, @Nonnull PGPSecretKeyRing... keyRings) throws KeyValidationException {
return new SignWithImpl().signWith(decryptor, keyRings);
}
@Override
public AdditionalSignWith signWith(@Nonnull SecretKeyRingProtector decryptor, @Nonnull PGPSecretKeyRingCollection keyRings) {
return new SignWithImpl().signWith(decryptor, keyRings);
}
@Override
public AdditionalSignWith signInlineWith(@Nonnull SecretKeyRingProtector secretKeyDecryptor, @Nonnull PGPSecretKeyRing signingKey, String userId, DocumentSignatureType signatureType) throws PGPException {
return new SignWithImpl().signInlineWith(secretKeyDecryptor, signingKey, userId, signatureType);
}
@Override
public AdditionalSignWith signDetachedWith(@Nonnull SecretKeyRingProtector secretKeyDecryptor, @Nonnull PGPSecretKeyRing signingKey, String userId, DocumentSignatureType signatureType) throws PGPException {
return new SignWithImpl().signDetachedWith(secretKeyDecryptor, signingKey, userId, signatureType);
}
}
class SignWithOrDontSignImpl extends SignWithImpl implements SignWithOrDontSign {
@Override
public Armor doNotSign() {
return new ArmorImpl();
}
@Override
public DocumentType signWith(@Nonnull SecretKeyRingProtector decryptor, @Nonnull PGPSecretKeyRing... keyRings) {
return new SignWithImpl().signWith(decryptor, keyRings);
}
@Override
public DocumentType signWith(@Nonnull SecretKeyRingProtector decryptor, @Nonnull PGPSecretKeyRingCollection keyRings) {
return new SignWithImpl().signWith(decryptor, keyRings);
}
}
class SignWithImpl implements SignWith {
@Override
public DocumentType signWith(@Nonnull SecretKeyRingProtector decryptor,
@Nonnull PGPSecretKeyRing... keyRings) {
if (keyRings.length == 0) {
throw new IllegalArgumentException("Signing key list MUST NOT be empty.");
public AdditionalSignWith signWith(@Nonnull SecretKeyRingProtector decryptor,
@Nonnull PGPSecretKeyRing... keyRings)
throws KeyValidationException {
for (PGPSecretKeyRing secretKeyRing : keyRings) {
signingOptions.addInlineSignature(decryptor, secretKeyRing, DocumentSignatureType.BINARY_DOCUMENT);
}
for (PGPSecretKeyRing ring : keyRings) {
Map<SubkeyIdentifier, PGPSecretKeyRing> signingKeys = new ConcurrentHashMap<>();
for (Iterator<PGPSecretKey> i = ring.getSecretKeys(); i.hasNext(); ) {
PGPSecretKey s = i.next();
if (EncryptionBuilder.this.signingKeySelector().accept(s)) {
signingKeys.put(new SubkeyIdentifier(ring, s.getKeyID()), ring);
}
}
if (signingKeys.isEmpty()) {
throw new IllegalArgumentException("No suitable signing key found in the key ring " + new OpenPgpV4Fingerprint(ring));
}
EncryptionBuilder.this.signingKeys.putAll(signingKeys);
}
EncryptionBuilder.this.signingKeysDecryptor = decryptor;
return new DocumentTypeImpl();
return new AdditionalSignWithImpl();
}
@Override
public DocumentType signWith(@Nonnull SecretKeyRingProtector decryptor, @Nonnull PGPSecretKeyRingCollection keyRings) {
Iterator<PGPSecretKeyRing> iterator = keyRings.iterator();
if (!iterator.hasNext()) {
throw new IllegalArgumentException("Signing key collection MUST NOT be empty.");
public AdditionalSignWith signWith(@Nonnull SecretKeyRingProtector decryptor, @Nonnull PGPSecretKeyRingCollection keyRings)
throws KeyValidationException {
for (PGPSecretKeyRing key : keyRings) {
signingOptions.addInlineSignature(decryptor, key, DocumentSignatureType.BINARY_DOCUMENT);
}
while (iterator.hasNext()) {
PGPSecretKeyRing ring = iterator.next();
Map<SubkeyIdentifier, PGPSecretKeyRing> signingKeys = new ConcurrentHashMap<>();
for (Iterator<PGPSecretKey> i = ring.getSecretKeys(); i.hasNext(); ) {
PGPSecretKey s = i.next();
if (EncryptionBuilder.this.signingKeySelector().accept(s)) {
signingKeys.put(new SubkeyIdentifier(ring, s.getKeyID()), ring);
}
}
return new AdditionalSignWithImpl();
}
if (signingKeys.isEmpty()) {
throw new IllegalArgumentException("No suitable signing key found in the key ring " + new OpenPgpV4Fingerprint(ring));
}
@Override
public AdditionalSignWith signInlineWith(@Nonnull SecretKeyRingProtector secretKeyDecryptor,
@Nonnull PGPSecretKeyRing signingKey,
String userId,
DocumentSignatureType signatureType)
throws KeyValidationException, PGPException {
signingOptions.addInlineSignature(secretKeyDecryptor, signingKey, userId, signatureType);
return new AdditionalSignWithImpl();
}
EncryptionBuilder.this.signingKeys.putAll(signingKeys);
}
EncryptionBuilder.this.signingKeysDecryptor = decryptor;
return new DocumentTypeImpl();
@Override
public AdditionalSignWith signDetachedWith(@Nonnull SecretKeyRingProtector secretKeyDecryptor,
@Nonnull PGPSecretKeyRing signingKey,
String userId,
DocumentSignatureType signatureType)
throws PGPException, KeyValidationException {
signingOptions.addInlineSignature(secretKeyDecryptor, signingKey, userId, signatureType);
return new AdditionalSignWithImpl();
}
}
class DocumentTypeImpl implements DocumentType {
class AdditionalSignWithImpl implements AdditionalSignWith {
@Override
public Armor signBinaryDocument() {
EncryptionBuilder.this.signatureType = SignatureType.BINARY_DOCUMENT;
return new ArmorImpl();
public SignWith and() {
return new SignWithImpl();
}
@Override
public Armor signCanonicalText() {
EncryptionBuilder.this.signatureType = SignatureType.CANONICAL_TEXT_DOCUMENT;
return new ArmorImpl();
public EncryptionStream asciiArmor() throws IOException, PGPException {
return new ArmorImpl().asciiArmor();
}
@Override
public EncryptionStream noArmor() throws IOException, PGPException {
return new ArmorImpl().noArmor();
}
}
@ -329,70 +220,87 @@ public class EncryptionBuilder implements EncryptionBuilderInterface {
@Override
public EncryptionStream asciiArmor() throws IOException, PGPException {
EncryptionBuilder.this.asciiArmor = true;
assignProducerOptions();
options.setAsciiArmor(true);
return build();
}
@Override
public EncryptionStream noArmor() throws IOException, PGPException {
EncryptionBuilder.this.asciiArmor = false;
assignProducerOptions();
options.setAsciiArmor(false);
return build();
}
private EncryptionStream build() throws IOException, PGPException {
Map<SubkeyIdentifier, Tuple<PGPSecretKeyRing, PGPPrivateKey>> privateKeys = new ConcurrentHashMap<>();
for (SubkeyIdentifier signingKey : signingKeys.keySet()) {
PGPSecretKeyRing secretKeyRing = signingKeys.get(signingKey);
PGPSecretKey secretKey = secretKeyRing.getSecretKey(signingKey.getSubkeyFingerprint().getKeyId());
PBESecretKeyDecryptor decryptor = signingKeysDecryptor.getDecryptor(secretKey.getKeyID());
PGPPrivateKey privateKey = UnlockSecretKey.unlockSecretKey(secretKey, decryptor);
privateKeys.put(signingKey, new Tuple<>(secretKeyRing, privateKey));
}
return new EncryptionStream(
EncryptionBuilder.this.outputStream,
EncryptionBuilder.this.encryptionKeys,
EncryptionBuilder.this.encryptionPassphrases,
EncryptionBuilder.this.detachedSignature,
signatureType,
privateKeys,
EncryptionBuilder.this.symmetricKeyAlgorithm,
EncryptionBuilder.this.hashAlgorithm,
EncryptionBuilder.this.compressionAlgorithm,
EncryptionBuilder.this.asciiArmor,
EncryptionBuilder.this.options,
fileInfo);
}
}
PublicKeySelectionStrategy encryptionKeySelector() {
KeyFlag[] flags = mapPurposeToKeyFlags(purpose);
return new And.PubKeySelectionStrategy(
new NoRevocation.PubKeySelectionStrategy(),
new EncryptionKeySelectionStrategy(flags));
}
SecretKeySelectionStrategy signingKeySelector() {
return new And.SecKeySelectionStrategy(
new NoRevocation.SecKeySelectionStrategy(),
new SignatureKeySelectionStrategy());
}
private static KeyFlag[] mapPurposeToKeyFlags(EncryptionStream.Purpose purpose) {
KeyFlag[] flags;
switch (purpose) {
case COMMUNICATIONS:
flags = new KeyFlag[] {KeyFlag.ENCRYPT_COMMS};
break;
case STORAGE:
flags = new KeyFlag[] {KeyFlag.ENCRYPT_STORAGE};
break;
case STORAGE_AND_COMMUNICATIONS:
flags = new KeyFlag[] {KeyFlag.ENCRYPT_COMMS, KeyFlag.ENCRYPT_STORAGE};
break;
default:
throw new AssertionError("Illegal purpose enum value encountered.");
private void assignProducerOptions() {
if (encryptionOptions != null && signingOptions != null) {
options = ProducerOptions.signAndEncrypt(encryptionOptions, signingOptions);
} else if (encryptionOptions != null) {
options = ProducerOptions.encrypt(encryptionOptions);
} else if (signingOptions != null) {
options = ProducerOptions.sign(signingOptions);
} else {
options = ProducerOptions.noEncryptionNoSigning();
}
}
return flags;
}
/**
* Negotiate the {@link SymmetricKeyAlgorithm} used for message encryption.
* If the user chose to set an override ({@link EncryptionOptions#overrideEncryptionAlgorithm(SymmetricKeyAlgorithm)}, use that.
* Otherwise find an algorithm which is acceptable for all recipients.
* If no consensus can be reached, use {@link Policy.SymmetricKeyAlgorithmPolicy#getDefaultSymmetricKeyAlgorithm()}.
*
* @param encryptionOptions encryption options
* @return negotiated symmetric key algorithm
*/
public static SymmetricKeyAlgorithm negotiateSymmetricEncryptionAlgorithm(EncryptionOptions encryptionOptions) {
SymmetricKeyAlgorithm encryptionAlgorithmOverride = encryptionOptions.getEncryptionAlgorithmOverride();
if (encryptionAlgorithmOverride != null) {
return encryptionAlgorithmOverride;
}
// TODO: Negotiation
return PGPainless.getPolicy().getSymmetricKeyAlgorithmPolicy().getDefaultSymmetricKeyAlgorithm();
}
/**
* Negotiate the {@link HashAlgorithm} used for signatures.
*
* If we encrypt and sign, we look at the recipients keys to determine which algorithm to use.
* If we only sign, we look at the singing keys preferences instead.
*
* @param encryptionOptions encryption options (recipients keys)
* @param signingOptions signing options (signing keys)
* @return negotiated hash algorithm
*/
public static HashAlgorithm negotiateSignatureHashAlgorithm(EncryptionOptions encryptionOptions, SigningOptions signingOptions) {
HashAlgorithm hashAlgorithmOverride = signingOptions.getHashAlgorithmOverride();
if (hashAlgorithmOverride != null) {
return hashAlgorithmOverride;
}
// TODO: Negotiation
return PGPainless.getPolicy().getSignatureHashAlgorithmPolicy().defaultHashAlgorithm();
}
public static CompressionAlgorithm negotiateCompressionAlgorithm(ProducerOptions producerOptions) {
CompressionAlgorithm compressionAlgorithmOverride = producerOptions.getCompressionAlgorithmOverride();
if (compressionAlgorithmOverride != null) {
return compressionAlgorithmOverride;
}
// TODO: Negotiation
return PGPainless.getPolicy().getCompressionAlgorithmPolicy().defaultCompressionAlgorithm();
}
}

View file

@ -25,11 +25,10 @@ import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
import org.pgpainless.algorithm.CompressionAlgorithm;
import org.pgpainless.algorithm.HashAlgorithm;
import org.pgpainless.algorithm.DocumentSignatureType;
import org.pgpainless.algorithm.StreamEncoding;
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
import org.pgpainless.decryption_verification.OpenPgpMetadata;
import org.pgpainless.exception.KeyValidationException;
import org.pgpainless.key.protection.SecretKeyRingProtector;
import org.pgpainless.util.Passphrase;
@ -42,7 +41,7 @@ public interface EncryptionBuilderInterface {
* @param outputStream output stream of the plain data.
* @return api handle
*/
default ToRecipients onOutputStream(@Nonnull OutputStream outputStream) {
default ToRecipientsOrNoEncryption onOutputStream(@Nonnull OutputStream outputStream) {
return onOutputStream(outputStream, OpenPgpMetadata.FileInfo.binaryStream());
}
/**
@ -55,7 +54,7 @@ public interface EncryptionBuilderInterface {
*
* @deprecated use {@link #onOutputStream(OutputStream, OpenPgpMetadata.FileInfo)} instead.
*/
default ToRecipients onOutputStream(@Nonnull OutputStream outputStream, boolean forYourEyesOnly) {
default ToRecipientsOrNoEncryption onOutputStream(@Nonnull OutputStream outputStream, boolean forYourEyesOnly) {
return onOutputStream(outputStream, forYourEyesOnly ? OpenPgpMetadata.FileInfo.forYourEyesOnly() : OpenPgpMetadata.FileInfo.binaryStream());
}
@ -70,7 +69,7 @@ public interface EncryptionBuilderInterface {
*
* @deprecated use {@link #onOutputStream(OutputStream, OpenPgpMetadata.FileInfo)} instead.
*/
default ToRecipients onOutputStream(@Nonnull OutputStream outputStream, String fileName, boolean forYourEyesOnly) {
default ToRecipientsOrNoEncryption onOutputStream(@Nonnull OutputStream outputStream, String fileName, boolean forYourEyesOnly) {
return onOutputStream(outputStream, new OpenPgpMetadata.FileInfo(forYourEyesOnly ? "_CONSOLE" : fileName, new Date(), StreamEncoding.BINARY));
}
@ -82,102 +81,99 @@ public interface EncryptionBuilderInterface {
* @param fileInfo file information
* @return api handle
*/
ToRecipients onOutputStream(@Nonnull OutputStream outputStream, OpenPgpMetadata.FileInfo fileInfo);
ToRecipientsOrNoEncryption onOutputStream(@Nonnull OutputStream outputStream, OpenPgpMetadata.FileInfo fileInfo);
interface ToRecipients {
interface ToRecipientsOrNoEncryption extends ToRecipients {
/**
* Pass in a list of trusted public key rings of the recipients.
* Create an {@link EncryptionStream} with the given options (recipients, signers, algorithms...).
*
* @param keys recipient keys for which the message will be encrypted.
* @return api handle
* @param options options
* @return encryption strea
*/
WithAlgorithms toRecipients(@Nonnull PGPPublicKeyRing... keys);
/**
* Pass in a list of trusted public key ring collections of the recipients.
*
* @param keys recipient keys for which the message will be encrypted.
* @return api handle
*/
WithAlgorithms toRecipients(@Nonnull PGPPublicKeyRingCollection... keys);
/**
* Encrypt to one or more symmetric passphrases.
* Note that the passphrases MUST NOT be empty.
*
* @param passphrases passphrase
* @return api handle
*/
WithAlgorithms forPassphrases(Passphrase... passphrases);
EncryptionStream withOptions(ProducerOptions options) throws PGPException, IOException;
/**
* Instruct the {@link EncryptionStream} to not encrypt any data.
*
* @return api handle
*/
DetachedSign doNotEncrypt();
SignWithOrDontSign doNotEncrypt();
}
interface ToRecipients {
/**
* Encrypt for the given valid public key.
* TODO: Explain the difference between this and {@link #toRecipient(PGPPublicKeyRing, String)}.
*
* @param key recipient key for which the message will be encrypted.
* @return api handle
*/
AdditionalRecipients toRecipient(@Nonnull PGPPublicKeyRing key);
/**
* Encrypt for the given valid key using the provided user-id signature to determine preferences.
*
* @param key public key
* @param userId user-id which is used to select the correct encryption parameters based on preferences.
* @return api handle
*/
AdditionalRecipients toRecipient(@Nonnull PGPPublicKeyRing key, @Nonnull String userId);
/**
* Encrypt for the first valid key in the provided keys collection which has a valid user-id that matches
* the provided userId.
* The user-id is also used to determine encryption preferences.
*
* @param keys collection of keys
* @param userId user-id used to select the correct key
* @return api handle
*/
AdditionalRecipients toRecipient(@Nonnull PGPPublicKeyRingCollection keys, @Nonnull String userId);
/**
* Encrypt for all valid public keys in the provided collection.
* If any key is not eligible for encryption (e.g. expired, revoked...), an exception will be thrown.
* TODO: which exception?
*
* @param keys collection of public keys
* @return api handle
*/
AdditionalRecipients toRecipients(@Nonnull PGPPublicKeyRingCollection keys);
/**
* Symmetrically encrypt the message using a passphrase.
* Note that the passphrase MUST NOT be empty.
*
* @param passphrase passphrase
* @return api handle
*/
AdditionalRecipients forPassphrase(Passphrase passphrase);
}
interface WithAlgorithms {
interface AdditionalRecipients {
/**
* Add our own public key to the list of recipient keys.
*
* @param keys own public keys
* @return api handle
*/
WithAlgorithms andToSelf(@Nonnull PGPPublicKeyRing... keys);
/**
* Add our own public keys to the list of recipient keys.
*
* @param keys own public keys
* @return api handle
*/
WithAlgorithms andToSelf(@Nonnull PGPPublicKeyRingCollection keys);
/**
* Specify which algorithms should be used for the encryption.
*
* @param symmetricKeyAlgorithm symmetric algorithm for the session key
* @param hashAlgorithm hash algorithm
* @param compressionAlgorithm compression algorithm
* @return api handle
*/
DetachedSign usingAlgorithms(@Nonnull SymmetricKeyAlgorithm symmetricKeyAlgorithm,
@Nonnull HashAlgorithm hashAlgorithm,
@Nonnull CompressionAlgorithm compressionAlgorithm);
/**
* Use a suite of algorithms that are considered secure.
* Add an additional recipient key/passphrase or configure signing.
*
* @return api handle
*/
DetachedSign usingSecureAlgorithms();
ToRecipients and();
ToRecipientsOrSign and();
}
interface DetachedSign extends SignWith {
/**
* Instruct the {@link EncryptionStream} to generate detached signatures instead of One-Pass-Signatures.
* Those can be retrieved later via {@link OpenPgpMetadata#getSignatures()}.
*
* @return api handle
*/
SignWith createDetachedSignature();
// Allow additional recipient or signing configuration
interface ToRecipientsOrSign extends ToRecipients, SignWithOrDontSign {
}
// Allow signing configuration or no signing at all
interface SignWithOrDontSign extends SignWith {
/**
* Do not sign the plain data at all.
*
* @return api handle
*/
Armor doNotSign();
}
interface SignWith {
@ -186,21 +182,104 @@ public interface EncryptionBuilderInterface {
* Pass in a list of secret keys used for signing, along with a {@link SecretKeyRingProtector} used to unlock
* the secret keys.
*
* @deprecated use {@link #signInlineWith(SecretKeyRingProtector, PGPSecretKeyRing)} instead.
* @param decryptor {@link SecretKeyRingProtector} used to unlock the secret keys
* @param keyRings secret keys used for signing
* @return api handle
*/
DocumentType signWith(@Nonnull SecretKeyRingProtector decryptor, @Nonnull PGPSecretKeyRing... keyRings);
@Deprecated
AdditionalSignWith signWith(@Nonnull SecretKeyRingProtector decryptor, @Nonnull PGPSecretKeyRing... keyRings) throws KeyValidationException;
DocumentType signWith(@Nonnull SecretKeyRingProtector decryptor, @Nonnull PGPSecretKeyRingCollection keyRings);
/**
* Sign inline using the passed in secret keys.
*
* @deprecated use {@link #signInlineWith(SecretKeyRingProtector, PGPSecretKeyRing)} instead.
* @param decryptor for unlocking the secret keys
* @param keyRings secret keys
* @return api handle
*/
@Deprecated
AdditionalSignWith signWith(@Nonnull SecretKeyRingProtector decryptor, @Nonnull PGPSecretKeyRingCollection keyRings) throws KeyValidationException;
/**
* Create an inline signature using the provided secret key.
* The signature will be of type {@link DocumentSignatureType#BINARY_DOCUMENT}.
*
* @param secretKeyDecryptor for unlocking the secret key
* @param signingKey signing key
* @return api handle
*/
default AdditionalSignWith signInlineWith(@Nonnull SecretKeyRingProtector secretKeyDecryptor, @Nonnull PGPSecretKeyRing signingKey) throws PGPException, KeyValidationException {
return signInlineWith(secretKeyDecryptor, signingKey, null);
}
/**
* Create an inline signature using the provided secret key.
* If userId is not null, the preferences of the matching user-id on the key will be used for signing.
* The signature will be of type {@link DocumentSignatureType#BINARY_DOCUMENT}.
*
* @param secretKeyDecryptor for unlocking the secret key
* @param signingKey signing key
* @param userId userId whose preferences shall be used for signing
* @return api handle
*/
default AdditionalSignWith signInlineWith(@Nonnull SecretKeyRingProtector secretKeyDecryptor, @Nonnull PGPSecretKeyRing signingKey, String userId) throws PGPException, KeyValidationException {
return signInlineWith(secretKeyDecryptor, signingKey, userId, DocumentSignatureType.BINARY_DOCUMENT);
}
/**
* Create an inline signature using the provided secret key with the algorithm preferences of the provided user-id.
*
* @param secretKeyDecryptor for unlocking the secret key
* @param signingKey signing key
* @param userId user-id whose preferences shall be used for signing
* @param signatureType signature type
* @return api handle
*/
AdditionalSignWith signInlineWith(@Nonnull SecretKeyRingProtector secretKeyDecryptor, @Nonnull PGPSecretKeyRing signingKey, String userId, DocumentSignatureType signatureType) throws KeyValidationException, PGPException;
/**
* Create a detached signature using the provided secret key.
*
* @param secretKeyDecryptor for unlocking the secret key
* @param signingKey signing key
* @return api handle
*/
default AdditionalSignWith signDetachedWith(@Nonnull SecretKeyRingProtector secretKeyDecryptor, @Nonnull PGPSecretKeyRing signingKey) throws PGPException, KeyValidationException {
return signDetachedWith(secretKeyDecryptor, signingKey, null);
}
/**
* Create a detached signature using the provided secret key with the algorithm preferences of the provided user-id.
*
* @param secretKeyDecryptor for unlocking the secret key
* @param signingKey signing key
* @param userId user-id whose preferences shall be used for signing
* @return api handle
*/
default AdditionalSignWith signDetachedWith(@Nonnull SecretKeyRingProtector secretKeyDecryptor, @Nonnull PGPSecretKeyRing signingKey, String userId) throws PGPException, KeyValidationException {
return signDetachedWith(secretKeyDecryptor, signingKey, userId, DocumentSignatureType.BINARY_DOCUMENT);
}
/**
* Create a detached signature using the provided secret key with the algorithm preferences of the provided user-id.
*
* @param secretKeyDecryptor for unlocking the secret key
* @param signingKey signing key
* @param userId user-id whose preferences shall be used for signing
* @param signatureType type of the signature
* @return api handle
*/
AdditionalSignWith signDetachedWith(@Nonnull SecretKeyRingProtector secretKeyDecryptor, @Nonnull PGPSecretKeyRing signingKey, String userId, DocumentSignatureType signatureType) throws PGPException, KeyValidationException;
}
interface DocumentType {
Armor signBinaryDocument();
Armor signCanonicalText();
interface AdditionalSignWith extends Armor {
/**
* Add an additional signing key/method.
*
* @return api handle
*/
SignWith and();
}
interface Armor {

View file

@ -0,0 +1,129 @@
/*
* Copyright 2021 Paul Schaub.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.pgpainless.encryption_signing;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.operator.PBEKeyEncryptionMethodGenerator;
import org.bouncycastle.openpgp.operator.PGPKeyEncryptionMethodGenerator;
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
import org.pgpainless.implementation.ImplementationFactory;
import org.pgpainless.key.SubkeyIdentifier;
import org.pgpainless.key.info.KeyRingInfo;
import org.pgpainless.util.Passphrase;
public class EncryptionOptions {
private final EncryptionStream.Purpose purpose;
private final Set<PGPKeyEncryptionMethodGenerator> encryptionMethods = new LinkedHashSet<>();
private final Set<SubkeyIdentifier> encryptionKeys = new LinkedHashSet<>();
private final Map<SubkeyIdentifier, KeyRingInfo> keyRingInfo = new HashMap<>();
private SymmetricKeyAlgorithm encryptionAlgorithmOverride = null;
public EncryptionOptions(EncryptionStream.Purpose purpose) {
this.purpose = purpose;
}
/**
* Add a recipient by providing a key and recipient user-id.
* The user-id is used to determine the recipients preferences (algorithms etc.).
*
* @param key key ring
* @param userId user id
*/
public void addRecipient(PGPPublicKeyRing key, String userId) {
KeyRingInfo info = new KeyRingInfo(key, new Date());
PGPPublicKey encryptionSubkey = info.getEncryptionSubkey(userId, purpose);
if (encryptionSubkey == null) {
throw new AssertionError("Key has no encryption subkey.");
}
addRecipientKey(key, encryptionSubkey);
}
/**
* Add a recipient by providing a key.
*
* @param key key ring
*/
public void addRecipient(PGPPublicKeyRing key) {
KeyRingInfo info = new KeyRingInfo(key, new Date());
PGPPublicKey encryptionSubkey = info.getEncryptionSubkey(purpose);
if (encryptionSubkey == null) {
throw new AssertionError("Key has no encryption subkey.");
}
addRecipientKey(key, encryptionSubkey);
}
private void addRecipientKey(PGPPublicKeyRing keyRing, PGPPublicKey key) {
encryptionKeys.add(new SubkeyIdentifier(keyRing, key.getKeyID()));
PGPKeyEncryptionMethodGenerator encryptionMethod = ImplementationFactory
.getInstance().getPublicKeyKeyEncryptionMethodGenerator(key);
addEncryptionMethod(encryptionMethod);
}
/**
* Add a symmetric passphrase which the message will be encrypted to.
*
* @param passphrase passphrase
*/
public void addPassphrase(Passphrase passphrase) {
if (passphrase.isEmpty()) {
throw new IllegalArgumentException("Passphrase must not be empty.");
}
PBEKeyEncryptionMethodGenerator encryptionMethod = ImplementationFactory
.getInstance().getPBEKeyEncryptionMethodGenerator(passphrase);
addEncryptionMethod(encryptionMethod);
}
/**
* Add an {@link PGPKeyEncryptionMethodGenerator} which will be used to encrypt the message.
* Method generators are either {@link PBEKeyEncryptionMethodGenerator} (passphrase)
* or {@link PGPKeyEncryptionMethodGenerator} (public key).
*
* This method is intended for advanced users to allow encryption for specific subkeys.
* This can come in handy for example if data needs to be encrypted to a subkey that's ignored by PGPainless.
*
* @param encryptionMethod encryption method
*/
public void addEncryptionMethod(PGPKeyEncryptionMethodGenerator encryptionMethod) {
encryptionMethods.add(encryptionMethod);
}
public Set<PGPKeyEncryptionMethodGenerator> getEncryptionMethods() {
return new HashSet<>(encryptionMethods);
}
public Set<SubkeyIdentifier> getEncryptionKeyIdentifiers() {
return new HashSet<>(encryptionKeys);
}
public SymmetricKeyAlgorithm getEncryptionAlgorithmOverride() {
return encryptionAlgorithmOverride;
}
public void overrideEncryptionAlgorithm(SymmetricKeyAlgorithm encryptionAlgorithm) {
this.encryptionAlgorithmOverride = encryptionAlgorithm;
}
}

View file

@ -0,0 +1,127 @@
/*
* Copyright 2021 Paul Schaub.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.pgpainless.encryption_signing;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import org.bouncycastle.openpgp.PGPSignature;
import org.pgpainless.algorithm.CompressionAlgorithm;
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
import org.pgpainless.decryption_verification.OpenPgpMetadata;
import org.pgpainless.key.SubkeyIdentifier;
import org.pgpainless.util.MultiMap;
public final class EncryptionResult {
private final SymmetricKeyAlgorithm encryptionAlgorithm;
private final CompressionAlgorithm compressionAlgorithm;
private final MultiMap<SubkeyIdentifier, PGPSignature> detachedSignatures;
private final Set<SubkeyIdentifier> recipients;
private final OpenPgpMetadata.FileInfo fileInfo;
private EncryptionResult(SymmetricKeyAlgorithm encryptionAlgorithm,
CompressionAlgorithm compressionAlgorithm,
MultiMap<SubkeyIdentifier, PGPSignature> detachedSignatures,
Set<SubkeyIdentifier> recipients,
OpenPgpMetadata.FileInfo fileInfo) {
this.encryptionAlgorithm = encryptionAlgorithm;
this.compressionAlgorithm = compressionAlgorithm;
this.detachedSignatures = detachedSignatures;
this.recipients = Collections.unmodifiableSet(recipients);
this.fileInfo = fileInfo;
}
@Deprecated
public SymmetricKeyAlgorithm getSymmetricKeyAlgorithm() {
return getEncryptionAlgorithm();
}
public SymmetricKeyAlgorithm getEncryptionAlgorithm() {
return encryptionAlgorithm;
}
public CompressionAlgorithm getCompressionAlgorithm() {
return compressionAlgorithm;
}
public MultiMap<SubkeyIdentifier, PGPSignature> getDetachedSignatures() {
return detachedSignatures;
}
public Set<SubkeyIdentifier> getRecipients() {
return recipients;
}
public OpenPgpMetadata.FileInfo getFileInfo() {
return fileInfo;
}
public static Builder builder() {
return new Builder();
}
public static class Builder {
private SymmetricKeyAlgorithm encryptionAlgorithm;
private CompressionAlgorithm compressionAlgorithm;
private final MultiMap<SubkeyIdentifier, PGPSignature> detachedSignatures = new MultiMap<>();
private Set<SubkeyIdentifier> recipients = new HashSet<>();
private OpenPgpMetadata.FileInfo fileInfo;
public Builder setEncryptionAlgorithm(SymmetricKeyAlgorithm encryptionAlgorithm) {
this.encryptionAlgorithm = encryptionAlgorithm;
return this;
}
public Builder setCompressionAlgorithm(CompressionAlgorithm compressionAlgorithm) {
this.compressionAlgorithm = compressionAlgorithm;
return this;
}
public Builder addRecipient(SubkeyIdentifier recipient) {
this.recipients.add(recipient);
return this;
}
public Builder addDetachedSignature(SubkeyIdentifier signingSubkeyIdentifier, PGPSignature detachedSignature) {
this.detachedSignatures.put(signingSubkeyIdentifier, detachedSignature);
return this;
}
public Builder setFileInfo(OpenPgpMetadata.FileInfo fileInfo) {
this.fileInfo = fileInfo;
return this;
}
public EncryptionResult build() {
if (encryptionAlgorithm == null) {
throw new IllegalStateException("Encryption algorithm not set.");
}
if (compressionAlgorithm == null) {
throw new IllegalStateException("Compression algorithm not set.");
}
if (fileInfo == null) {
throw new IllegalStateException("File info not set.");
}
return new EncryptionResult(encryptionAlgorithm, compressionAlgorithm, detachedSignatures, recipients, fileInfo);
}
}
}

View file

@ -17,10 +17,6 @@ package org.pgpainless.encryption_signing;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Nonnull;
@ -31,29 +27,18 @@ import org.bouncycastle.openpgp.PGPCompressedDataGenerator;
import org.bouncycastle.openpgp.PGPEncryptedDataGenerator;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPLiteralDataGenerator;
import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.openpgp.PGPSignatureGenerator;
import org.bouncycastle.openpgp.operator.PBEKeyEncryptionMethodGenerator;
import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder;
import org.bouncycastle.openpgp.operator.PGPDataEncryptorBuilder;
import org.bouncycastle.openpgp.operator.PublicKeyKeyEncryptionMethodGenerator;
import org.bouncycastle.openpgp.operator.PGPKeyEncryptionMethodGenerator;
import org.bouncycastle.openpgp.operator.bc.BcPGPDataEncryptorBuilder;
import org.bouncycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder;
import org.pgpainless.algorithm.CompressionAlgorithm;
import org.pgpainless.algorithm.HashAlgorithm;
import org.pgpainless.algorithm.SignatureType;
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
import org.pgpainless.decryption_verification.OpenPgpMetadata;
import org.pgpainless.implementation.ImplementationFactory;
import org.pgpainless.key.SubkeyIdentifier;
import org.pgpainless.signature.DetachedSignature;
import org.pgpainless.util.ArmoredOutputStreamFactory;
import org.pgpainless.util.Passphrase;
import org.pgpainless.util.Tuple;
/**
* This class is based upon Jens Neuhalfen's Bouncy-GPG PGPEncryptingStream.
@ -81,70 +66,36 @@ public final class EncryptionStream extends OutputStream {
private static final Logger LOGGER = Logger.getLogger(EncryptionStream.class.getName());
private static final Level LEVEL = Level.FINE;
private final ProducerOptions options;
private final EncryptionResult.Builder resultBuilder = EncryptionResult.builder();
private boolean closed = false;
private static final int BUFFER_SIZE = 1 << 8;
private final SymmetricKeyAlgorithm symmetricKeyAlgorithm;
private final HashAlgorithm hashAlgorithm;
private final CompressionAlgorithm compressionAlgorithm;
private final Map<SubkeyIdentifier, PGPPublicKeyRing> encryptionKeys;
private final Set<Passphrase> encryptionPassphrases;
private final boolean detachedSignature;
private final SignatureType signatureType;
private final Map<SubkeyIdentifier, Tuple<PGPSecretKeyRing, PGPPrivateKey>> signingKeys;
private final boolean asciiArmor;
private final OpenPgpMetadata.Builder resultBuilder = OpenPgpMetadata.getBuilder();
private Map<SubkeyIdentifier, Tuple<PGPSecretKeyRing, PGPSignatureGenerator>> signatureGenerators = new ConcurrentHashMap<>();
private boolean closed = false;
OutputStream outermostStream = null;
OutputStream outermostStream;
private ArmoredOutputStream armorOutputStream = null;
private OutputStream publicKeyEncryptedStream = null;
private PGPCompressedDataGenerator compressedDataGenerator;
private BCPGOutputStream basicCompressionStream;
private PGPLiteralDataGenerator literalDataGenerator;
private OutputStream literalDataStream;
EncryptionStream(@Nonnull OutputStream targetOutputStream,
@Nonnull Map<SubkeyIdentifier, PGPPublicKeyRing> encryptionKeys,
@Nonnull Set<Passphrase> encryptionPassphrases,
boolean detachedSignature,
SignatureType signatureType,
@Nonnull Map<SubkeyIdentifier, Tuple<PGPSecretKeyRing, PGPPrivateKey>> signingKeys,
@Nonnull SymmetricKeyAlgorithm symmetricKeyAlgorithm,
@Nonnull HashAlgorithm hashAlgorithm,
@Nonnull CompressionAlgorithm compressionAlgorithm,
boolean asciiArmor,
@Nonnull ProducerOptions options,
@Nonnull OpenPgpMetadata.FileInfo fileInfo)
throws IOException, PGPException {
this.symmetricKeyAlgorithm = symmetricKeyAlgorithm;
this.hashAlgorithm = hashAlgorithm;
this.compressionAlgorithm = compressionAlgorithm;
this.encryptionKeys = Collections.unmodifiableMap(encryptionKeys);
this.encryptionPassphrases = Collections.unmodifiableSet(encryptionPassphrases);
this.detachedSignature = detachedSignature;
this.signatureType = signatureType;
this.signingKeys = Collections.unmodifiableMap(signingKeys);
this.asciiArmor = asciiArmor;
this.options = options;
outermostStream = targetOutputStream;
prepareArmor();
prepareEncryption();
prepareSigning();
prepareCompression();
prepareOnePassSignatures();
prepareLiteralDataProcessing(fileInfo);
prepareResultBuilder();
resultBuilder.setFileInfo(fileInfo);
}
private void prepareArmor() {
if (!asciiArmor) {
if (!options.isAsciiArmor()) {
LOGGER.log(LEVEL, "Encryption output will be binary");
return;
}
@ -155,14 +106,18 @@ public final class EncryptionStream extends OutputStream {
}
private void prepareEncryption() throws IOException, PGPException {
if (encryptionKeys.isEmpty() && encryptionPassphrases.isEmpty()) {
EncryptionOptions encryptionOptions = options.getEncryptionOptions();
if (encryptionOptions == null || encryptionOptions.getEncryptionMethods().isEmpty()) {
// No encryption options/methods -> no encryption
resultBuilder.setEncryptionAlgorithm(SymmetricKeyAlgorithm.NULL);
return;
}
LOGGER.log(LEVEL, "At least one encryption key is available -> encrypt using " + symmetricKeyAlgorithm);
SymmetricKeyAlgorithm encryptionAlgorithm = EncryptionBuilder.negotiateSymmetricEncryptionAlgorithm(encryptionOptions);
resultBuilder.setEncryptionAlgorithm(encryptionAlgorithm);
LOGGER.log(LEVEL, "Encrypt message using " + encryptionAlgorithm);
PGPDataEncryptorBuilder dataEncryptorBuilder =
ImplementationFactory.getInstance().getPGPDataEncryptorBuilder(symmetricKeyAlgorithm);
ImplementationFactory.getInstance().getPGPDataEncryptorBuilder(encryptionAlgorithm);
// Simplify once https://github.com/bcgit/bc-java/pull/859 is merged
if (dataEncryptorBuilder instanceof BcPGPDataEncryptorBuilder) {
((BcPGPDataEncryptorBuilder) dataEncryptorBuilder).setWithIntegrityPacket(true);
@ -172,46 +127,21 @@ public final class EncryptionStream extends OutputStream {
PGPEncryptedDataGenerator encryptedDataGenerator =
new PGPEncryptedDataGenerator(dataEncryptorBuilder);
for (SubkeyIdentifier keyIdentifier : encryptionKeys.keySet()) {
LOGGER.log(LEVEL, "Encrypt for key " + keyIdentifier);
PGPPublicKey key = encryptionKeys.get(keyIdentifier).getPublicKey(keyIdentifier.getSubkeyFingerprint().getKeyId());
PublicKeyKeyEncryptionMethodGenerator keyEncryption =
ImplementationFactory.getInstance().getPublicKeyKeyEncryptionMethodGenerator(key);
encryptedDataGenerator.addMethod(keyEncryption);
for (PGPKeyEncryptionMethodGenerator encryptionMethod : encryptionOptions.getEncryptionMethods()) {
encryptedDataGenerator.addMethod(encryptionMethod);
}
for (Passphrase passphrase : encryptionPassphrases) {
PBEKeyEncryptionMethodGenerator passphraseEncryption =
ImplementationFactory.getInstance().getPBEKeyEncryptionMethodGenerator(passphrase);
encryptedDataGenerator.addMethod(passphraseEncryption);
for (SubkeyIdentifier recipientSubkeyIdentifier : encryptionOptions.getEncryptionKeyIdentifiers()) {
resultBuilder.addRecipient(recipientSubkeyIdentifier);
}
publicKeyEncryptedStream = encryptedDataGenerator.open(outermostStream, new byte[BUFFER_SIZE]);
outermostStream = publicKeyEncryptedStream;
}
private void prepareSigning() throws PGPException {
if (signingKeys.isEmpty()) {
return;
}
LOGGER.log(LEVEL, "At least one signing key is available -> sign " + hashAlgorithm + " hash of message");
for (SubkeyIdentifier subkeyIdentifier : signingKeys.keySet()) {
LOGGER.log(LEVEL, "Sign using key " + subkeyIdentifier);
PGPPrivateKey privateKey = signingKeys.get(subkeyIdentifier).getSecond();
PGPContentSignerBuilder contentSignerBuilder = ImplementationFactory.getInstance()
.getPGPContentSignerBuilder(
privateKey.getPublicKeyPacket().getAlgorithm(),
hashAlgorithm.getAlgorithmId());
PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder);
signatureGenerator.init(signatureType.getCode(), privateKey);
signatureGenerators.put(subkeyIdentifier, new Tuple<>(signingKeys.get(subkeyIdentifier).getFirst(), signatureGenerator));
}
}
private void prepareCompression() throws IOException {
CompressionAlgorithm compressionAlgorithm = options.getCompressionAlgorithmOverride();
resultBuilder.setCompressionAlgorithm(compressionAlgorithm);
compressedDataGenerator = new PGPCompressedDataGenerator(
compressionAlgorithm.getAlgorithmId());
if (compressionAlgorithm == CompressionAlgorithm.UNCOMPRESSED) {
@ -224,9 +154,19 @@ public final class EncryptionStream extends OutputStream {
}
private void prepareOnePassSignatures() throws IOException, PGPException {
for (SubkeyIdentifier identifier : signatureGenerators.keySet()) {
PGPSignatureGenerator signatureGenerator = signatureGenerators.get(identifier).getSecond();
signatureGenerator.generateOnePassVersion(false).encode(outermostStream);
SigningOptions signingOptions = options.getSigningOptions();
if (signingOptions == null || signingOptions.getSigningMethods().isEmpty()) {
// No singing options/methods -> no signing
return;
}
for (SubkeyIdentifier identifier : signingOptions.getSigningMethods().keySet()) {
SigningOptions.SigningMethod signingMethod = signingOptions.getSigningMethods().get(identifier);
if (!signingMethod.isDetached()) {
PGPSignatureGenerator signatureGenerator = signingMethod.getSignatureGenerator();
signatureGenerator.generateOnePassVersion(false).encode(outermostStream);
}
}
}
@ -238,38 +178,41 @@ public final class EncryptionStream extends OutputStream {
fileInfo.getModificationDate(),
new byte[BUFFER_SIZE]);
outermostStream = literalDataStream;
}
private void prepareResultBuilder() {
for (SubkeyIdentifier recipient : encryptionKeys.keySet()) {
resultBuilder.addRecipientKeyId(recipient.getSubkeyFingerprint().getKeyId());
}
resultBuilder.setSymmetricKeyAlgorithm(symmetricKeyAlgorithm);
resultBuilder.setCompressionAlgorithm(compressionAlgorithm);
resultBuilder.setFileInfo(fileInfo);
}
@Override
public void write(int data) throws IOException {
outermostStream.write(data);
SigningOptions signingOptions = options.getSigningOptions();
if (signingOptions == null || signingOptions.getSigningMethods().isEmpty()) {
return;
}
for (SubkeyIdentifier signingKey : signatureGenerators.keySet()) {
PGPSignatureGenerator signatureGenerator = signatureGenerators.get(signingKey).getSecond();
for (SubkeyIdentifier signingKey : signingOptions.getSigningMethods().keySet()) {
SigningOptions.SigningMethod signingMethod = signingOptions.getSigningMethods().get(signingKey);
PGPSignatureGenerator signatureGenerator = signingMethod.getSignatureGenerator();
byte asByte = (byte) (data & 0xff);
signatureGenerator.update(asByte);
}
}
@Override
public void write(byte[] buffer) throws IOException {
public void write(@Nonnull byte[] buffer) throws IOException {
write(buffer, 0, buffer.length);
}
@Override
public void write(byte[] buffer, int off, int len) throws IOException {
public void write(@Nonnull byte[] buffer, int off, int len) throws IOException {
outermostStream.write(buffer, 0, len);
for (SubkeyIdentifier signingKey : signatureGenerators.keySet()) {
PGPSignatureGenerator signatureGenerator = signatureGenerators.get(signingKey).getSecond();
SigningOptions signingOptions = options.getSigningOptions();
if (signingOptions == null || signingOptions.getSigningMethods().isEmpty()) {
return;
}
for (SubkeyIdentifier signingKey : signingOptions.getSigningMethods().keySet()) {
SigningOptions.SigningMethod signingMethod = signingOptions.getSigningMethods().get(signingKey);
PGPSignatureGenerator signatureGenerator = signingMethod.getSignatureGenerator();
signatureGenerator.update(buffer, 0, len);
}
}
@ -314,19 +257,23 @@ public final class EncryptionStream extends OutputStream {
}
private void writeSignatures() throws PGPException, IOException {
for (SubkeyIdentifier signingKey : signatureGenerators.keySet()) {
PGPSignatureGenerator signatureGenerator = signatureGenerators.get(signingKey).getSecond();
SigningOptions signingOptions = options.getSigningOptions();
if (signingOptions == null || signingOptions.getSigningMethods().isEmpty()) {
return;
}
for (SubkeyIdentifier signingKey : signingOptions.getSigningMethods().keySet()) {
SigningOptions.SigningMethod signingMethod = signingOptions.getSigningMethods().get(signingKey);
PGPSignatureGenerator signatureGenerator = signingMethod.getSignatureGenerator();
PGPSignature signature = signatureGenerator.generate();
if (!detachedSignature) {
if (signingMethod.isDetached()) {
resultBuilder.addDetachedSignature(signingKey, signature);
} else {
signature.encode(outermostStream);
}
DetachedSignature detachedSignature = new DetachedSignature(
signature, signatureGenerators.get(signingKey).getFirst(), signingKey);
resultBuilder.addDetachedSignature(detachedSignature);
}
}
public OpenPgpMetadata getResult() {
public EncryptionResult getResult() {
if (!closed) {
throw new IllegalStateException("EncryptionStream must be closed before accessing the Result.");
}

View file

@ -0,0 +1,135 @@
/*
* Copyright 2021 Paul Schaub.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.pgpainless.encryption_signing;
import javax.annotation.Nullable;
import org.pgpainless.PGPainless;
import org.pgpainless.algorithm.CompressionAlgorithm;
public final class ProducerOptions {
private final EncryptionOptions encryptionOptions;
private final SigningOptions signingOptions;
private CompressionAlgorithm compressionAlgorithmOverride = PGPainless.getPolicy().getCompressionAlgorithmPolicy()
.defaultCompressionAlgorithm();
private boolean asciiArmor = true;
private ProducerOptions(EncryptionOptions encryptionOptions, SigningOptions signingOptions) {
this.encryptionOptions = encryptionOptions;
this.signingOptions = signingOptions;
}
/**
* Sign and encrypt some data.
*
* @param encryptionOptions encryption options
* @param signingOptions signing options
* @return builder
*/
public static ProducerOptions signAndEncrypt(EncryptionOptions encryptionOptions,
SigningOptions signingOptions) {
throwIfNull(encryptionOptions);
throwIfNull(signingOptions);
return new ProducerOptions(encryptionOptions, signingOptions);
}
/**
* Sign some data without encryption.
*
* @param signingOptions signing options
* @return builder
*/
public static ProducerOptions sign(SigningOptions signingOptions) {
throwIfNull(signingOptions);
return new ProducerOptions(null, signingOptions);
}
/**
* Encrypt some data without signing.
*
* @param encryptionOptions encryption options
* @return builder
*/
public static ProducerOptions encrypt(EncryptionOptions encryptionOptions) {
throwIfNull(encryptionOptions);
return new ProducerOptions(encryptionOptions, null);
}
public static ProducerOptions noEncryptionNoSigning() {
return new ProducerOptions(null, null);
}
private static void throwIfNull(EncryptionOptions encryptionOptions) {
if (encryptionOptions == null) {
throw new NullPointerException("EncryptionOptions cannot be null.");
}
}
private static void throwIfNull(SigningOptions signingOptions) {
if (signingOptions == null) {
throw new NullPointerException("SigningOptions cannot be null.");
}
}
/**
* Override which compression algorithm shall be used.
*
* @param compressionAlgorithm compression algorithm override
* @return builder
*/
public ProducerOptions overrideCompressionAlgorithm(CompressionAlgorithm compressionAlgorithm) {
if (compressionAlgorithm == null) {
throw new NullPointerException("Compression algorithm cannot be null.");
}
this.compressionAlgorithmOverride = compressionAlgorithm;
return this;
}
/**
* Specify, whether or not the result of the encryption/signing operation shall be ascii armored.
* The default value is true.
*
* @param asciiArmor ascii armor
* @return builder
*/
public ProducerOptions setAsciiArmor(boolean asciiArmor) {
this.asciiArmor = asciiArmor;
return this;
}
/**
* Return true if the output of the encryption/signing operation shall be ascii armored.
*
* @return ascii armored
*/
public boolean isAsciiArmor() {
return asciiArmor;
}
public CompressionAlgorithm getCompressionAlgorithmOverride() {
return compressionAlgorithmOverride;
}
public @Nullable EncryptionOptions getEncryptionOptions() {
return encryptionOptions;
}
public @Nullable SigningOptions getSigningOptions() {
return signingOptions;
}
}

View file

@ -0,0 +1,136 @@
/*
* Copyright 2021 Paul Schaub.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.pgpainless.encryption_signing;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSignatureGenerator;
import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder;
import org.pgpainless.algorithm.DocumentSignatureType;
import org.pgpainless.algorithm.HashAlgorithm;
import org.pgpainless.exception.KeyValidationException;
import org.pgpainless.implementation.ImplementationFactory;
import org.pgpainless.key.SubkeyIdentifier;
import org.pgpainless.key.info.KeyRingInfo;
import org.pgpainless.key.protection.SecretKeyRingProtector;
public final class SigningOptions {
public static final class SigningMethod {
private final PGPSignatureGenerator signatureGenerator;
private final boolean detached;
private SigningMethod(PGPSignatureGenerator signatureGenerator, boolean detached) {
this.signatureGenerator = signatureGenerator;
this.detached = detached;
}
public static SigningMethod inlineSignature(PGPSignatureGenerator signatureGenerator) {
return new SigningMethod(signatureGenerator, false);
}
public static SigningMethod detachedSignature(PGPSignatureGenerator signatureGenerator) {
return new SigningMethod(signatureGenerator, true);
}
public boolean isDetached() {
return detached;
}
public PGPSignatureGenerator getSignatureGenerator() {
return signatureGenerator;
}
}
private Map<SubkeyIdentifier, SigningMethod> signingMethods = new HashMap<>();
private HashAlgorithm hashAlgorithmOverride;
public void addInlineSignature(SecretKeyRingProtector secretKeyDecryptor,
PGPSecretKeyRing secretKey,
DocumentSignatureType signatureType)
throws KeyValidationException {
}
public void addInlineSignature(SecretKeyRingProtector secretKeyDecryptor,
PGPSecretKeyRing secretKey,
String userId,
DocumentSignatureType signatureType)
throws KeyValidationException, PGPException {
KeyRingInfo keyRingInfo = new KeyRingInfo(secretKey, new Date());
if (userId != null) {
if (!keyRingInfo.isUserIdValid(userId)) {
throw new KeyValidationException(userId, keyRingInfo.getCurrentUserIdCertification(userId), keyRingInfo.getUserIdRevocation(userId));
}
}
PGPPublicKey signingPubKey = keyRingInfo.getSigningSubkey();
if (signingPubKey == null) {
throw new AssertionError("Key has no valid signing key.");
}
PGPSecretKey signingSecKey = secretKey.getSecretKey(signingPubKey.getKeyID());
PGPPrivateKey signingSubkey = signingSecKey.extractPrivateKey(secretKeyDecryptor.getDecryptor(signingPubKey.getKeyID()));
List<HashAlgorithm> hashAlgorithms = keyRingInfo.getPreferredHashAlgorithms(userId, signingPubKey.getKeyID());
addSigningMethod(secretKey, signingSubkey, hashAlgorithms.get(0), signatureType, false);
}
private void addSigningMethod(PGPSecretKeyRing secretKey,
PGPPrivateKey signingSubkey,
HashAlgorithm hashAlgorithm,
DocumentSignatureType signatureType,
boolean detached)
throws PGPException {
SubkeyIdentifier signingKeyIdentifier = new SubkeyIdentifier(secretKey, signingSubkey.getKeyID());
PGPSignatureGenerator generator = createSignatureGenerator(signingSubkey, hashAlgorithm, signatureType);
SigningMethod signingMethod = detached ? SigningMethod.detachedSignature(generator) : SigningMethod.inlineSignature(generator);
signingMethods.put(signingKeyIdentifier, signingMethod);
}
private PGPSignatureGenerator createSignatureGenerator(PGPPrivateKey privateKey,
HashAlgorithm hashAlgorithm,
DocumentSignatureType signatureType)
throws PGPException {
int publicKeyAlgorithm = privateKey.getPublicKeyPacket().getAlgorithm();
PGPContentSignerBuilder signerBuilder = ImplementationFactory.getInstance()
.getPGPContentSignerBuilder(publicKeyAlgorithm, hashAlgorithm.getAlgorithmId());
PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(signerBuilder);
signatureGenerator.init(signatureType.getSignatureType().getCode(), privateKey);
return signatureGenerator;
}
public Map<SubkeyIdentifier, SigningMethod> getSigningMethods() {
return Collections.unmodifiableMap(signingMethods);
}
public SigningOptions overrideHashAlgorithm(HashAlgorithm hashAlgorithmOverride) {
this.hashAlgorithmOverride = hashAlgorithmOverride;
return this;
}
public HashAlgorithm getHashAlgorithmOverride() {
return hashAlgorithmOverride;
}
}

View file

@ -0,0 +1,25 @@
/*
* Copyright 2021 Paul Schaub.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.pgpainless.exception;
import org.bouncycastle.openpgp.PGPSignature;
public class KeyValidationException extends AssertionError {
public KeyValidationException(String userId, PGPSignature userIdSig, PGPSignature userIdRevocation) {
super("User-ID '" + userId + "' is not valid: Sig: " + userIdSig + " Rev: " + userIdRevocation);
}
}

View file

@ -0,0 +1,36 @@
package org.pgpainless.key;
import java.util.List;
import javax.annotation.Nullable;
import org.bouncycastle.openpgp.PGPSignature;
import org.pgpainless.algorithm.KeyFlag;
import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil;
public interface EvaluatedKeyRing {
PGPSignature getUserIdCertification(String userId);
PGPSignature getUserIdRevocation(String userId);
PGPSignature getSubkeyBinding(long subkeyId);
PGPSignature getSubkeyRevocation(long subkeyId);
default boolean isUserIdRevoked(String userId) {
return getUserIdRevocation(userId) != null;
}
default boolean isSubkeyRevoked(long subkeyId) {
return getSubkeyRevocation(subkeyId) != null;
}
default @Nullable List<KeyFlag> getUserIdKeyFlags(String userId) {
PGPSignature signature = getUserIdCertification(userId);
return SignatureSubpacketsUtil.parseKeyFlags(signature);
}
}

View file

@ -62,6 +62,14 @@ public class SubkeyIdentifier {
this.subkeyFingerprint = subkeyFingerprint;
}
public @Nonnull OpenPgpV4Fingerprint getFingerprint() {
return getSubkeyFingerprint();
}
public long getKeyId() {
return getSubkeyId();
}
/**
* Return the {@link OpenPgpV4Fingerprint} of the primary key of the identified key.
* This might be the same as {@link #getSubkeyFingerprint()} if the identified subkey is the primary key.
@ -72,6 +80,16 @@ public class SubkeyIdentifier {
return primaryKeyFingerprint;
}
/**
* Return the key id of the primary key of the identified key.
* This might be the same as {@link #getSubkeyId()} if the identified subkey is the primary key.
*
* @return primary key id
*/
public long getPrimaryKeyId() {
return getPrimaryKeyFingerprint().getKeyId();
}
/**
* Return the {@link OpenPgpV4Fingerprint} of the identified subkey.
*
@ -81,6 +99,15 @@ public class SubkeyIdentifier {
return subkeyFingerprint;
}
/**
* Return the key id of the identified subkey.
*
* @return subkey id
*/
public long getSubkeyId() {
return getSubkeyFingerprint().getKeyId();
}
@Override
public int hashCode() {
return primaryKeyFingerprint.hashCode() * 31 + subkeyFingerprint.hashCode();

View file

@ -20,12 +20,12 @@ import static org.pgpainless.util.CollectionUtils.iteratorToList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -37,9 +37,14 @@ import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSignature;
import org.pgpainless.PGPainless;
import org.pgpainless.algorithm.HashAlgorithm;
import org.pgpainless.algorithm.KeyFlag;
import org.pgpainless.algorithm.PublicKeyAlgorithm;
import org.pgpainless.encryption_signing.EncryptionStream;
import org.pgpainless.exception.KeyValidationException;
import org.pgpainless.key.OpenPgpV4Fingerprint;
import org.pgpainless.policy.Policy;
import org.pgpainless.signature.SignaturePicker;
import org.pgpainless.signature.SignatureUtils;
import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil;
@ -52,13 +57,7 @@ public class KeyRingInfo {
private static final Pattern PATTERN_EMAIL = Pattern.compile("[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,6}");
private final PGPKeyRing keys;
private final PGPSignature revocationSelfSignature;
private final PGPSignature mostRecentSelfSignature;
private final Map<String, PGPSignature> mostRecentUserIdSignatures = new ConcurrentHashMap<>();
private final Map<String, PGPSignature> mostRecentUserIdRevocations = new ConcurrentHashMap<>();
private final Map<Long, PGPSignature> mostRecentSubkeyBindings = new ConcurrentHashMap<>();
private final Map<Long, PGPSignature> mostRecentSubkeyRevocations = new ConcurrentHashMap<>();
private Signatures signatures;
/**
* Evaluate the key ring at creation time of the given signature.
@ -82,36 +81,7 @@ public class KeyRingInfo {
public KeyRingInfo(PGPKeyRing keys, Date validationDate) {
this.keys = keys;
revocationSelfSignature = SignaturePicker.pickCurrentRevocationSelfSignature(keys, validationDate);
mostRecentSelfSignature = SignaturePicker.pickCurrentDirectKeySelfSignature(keys, validationDate);
for (Iterator<String> it = keys.getPublicKey().getUserIDs(); it.hasNext(); ) {
String userId = it.next();
PGPSignature certification = SignaturePicker.pickCurrentUserIdCertificationSignature(keys, userId, validationDate);
if (certification != null) {
mostRecentUserIdSignatures.put(userId, certification);
}
PGPSignature revocation = SignaturePicker.pickCurrentUserIdRevocationSignature(keys, userId, validationDate);
if (revocation != null) {
mostRecentUserIdRevocations.put(userId, revocation);
}
}
Iterator<PGPPublicKey> publicKeys = keys.getPublicKeys();
publicKeys.next(); // Skip primary key
while (publicKeys.hasNext()) {
PGPPublicKey subkey = publicKeys.next();
PGPSignature bindingSig = SignaturePicker.pickCurrentSubkeyBindingSignature(keys, subkey, validationDate);
if (bindingSig != null) {
mostRecentSubkeyBindings.put(subkey.getKeyID(), bindingSig);
}
PGPSignature bindingRevocation = SignaturePicker.pickCurrentSubkeyBindingRevocationSignature(keys, subkey, validationDate);
if (bindingRevocation != null) {
mostRecentSubkeyRevocations.put(subkey.getKeyID(), bindingRevocation);
}
}
this.signatures = new Signatures(keys, validationDate, PGPainless.getPolicy());
}
/**
@ -142,11 +112,11 @@ public class KeyRingInfo {
}
if (publicKey == getPublicKey()) {
return revocationSelfSignature == null;
return signatures.primaryKeyRevocation == null;
}
PGPSignature binding = mostRecentSubkeyBindings.get(keyId);
PGPSignature revocation = mostRecentSubkeyRevocations.get(keyId);
PGPSignature binding = signatures.subkeyBindings.get(keyId);
PGPSignature revocation = signatures.subkeyRevocations.get(keyId);
return binding != null && revocation == null;
}
@ -225,7 +195,7 @@ public class KeyRingInfo {
String primaryUserId = null;
Date modificationDate = null;
for (String userId : getValidUserIds()) {
PGPSignature signature = mostRecentUserIdSignatures.get(userId);
PGPSignature signature = signatures.userIdCertifications.get(userId);
PrimaryUserID subpacket = SignatureSubpacketsUtil.getPrimaryUserId(signature);
if (subpacket != null && subpacket.isPrimaryUserID()) {
// if there are multiple primary userIDs, return most recently signed
@ -235,6 +205,11 @@ public class KeyRingInfo {
}
}
}
// Workaround for keys with only one user-id but no primary user-id packet.
if (primaryUserId == null) {
return getValidUserIds().get(0);
}
return primaryUserId;
}
@ -261,8 +236,8 @@ public class KeyRingInfo {
}
public boolean isUserIdValid(String userId) {
PGPSignature certification = mostRecentUserIdSignatures.get(userId);
PGPSignature revocation = mostRecentUserIdRevocations.get(userId);
PGPSignature certification = signatures.userIdCertifications.get(userId);
PGPSignature revocation = signatures.userIdRevocations.get(userId);
return certification != null && revocation == null;
}
@ -285,34 +260,35 @@ public class KeyRingInfo {
}
public PGPSignature getCurrentDirectKeySelfSignature() {
return mostRecentSelfSignature;
return signatures.primaryKeySelfSignature;
}
public PGPSignature getRevocationSelfSignature() {
return revocationSelfSignature;
return signatures.primaryKeyRevocation;
}
public PGPSignature getCurrentUserIdCertification(String userId) {
return mostRecentUserIdSignatures.get(userId);
return signatures.userIdCertifications.get(userId);
}
public PGPSignature getUserIdRevocation(String userId) {
return mostRecentUserIdRevocations.get(userId);
return signatures.userIdRevocations.get(userId);
}
public PGPSignature getCurrentSubkeyBindingSignature(long keyId) {
return mostRecentSubkeyBindings.get(keyId);
return signatures.subkeyBindings.get(keyId);
}
public PGPSignature getSubkeyRevocationSignature(long keyId) {
return mostRecentSubkeyRevocations.get(keyId);
return signatures.subkeyRevocations.get(keyId);
}
public List<KeyFlag> getKeyFlagsOf(long keyId) {
if (getPublicKey().getKeyID() == keyId) {
if (mostRecentSelfSignature != null) {
KeyFlags flags = SignatureSubpacketsUtil.getKeyFlags(mostRecentSelfSignature);
PGPSignature directKeySignature = getCurrentDirectKeySelfSignature();
if (directKeySignature != null) {
KeyFlags flags = SignatureSubpacketsUtil.getKeyFlags(directKeySignature);
if (flags != null) {
return KeyFlag.fromBitmask(flags.getFlags());
}
@ -320,7 +296,15 @@ public class KeyRingInfo {
String primaryUserId = getPrimaryUserId();
if (primaryUserId != null) {
KeyFlags flags = SignatureSubpacketsUtil.getKeyFlags(mostRecentUserIdSignatures.get(primaryUserId));
KeyFlags flags = SignatureSubpacketsUtil.getKeyFlags(getCurrentUserIdCertification(primaryUserId));
if (flags != null) {
return KeyFlag.fromBitmask(flags.getFlags());
}
}
} else {
PGPSignature bindingSignature = getCurrentSubkeyBindingSignature(keyId);
if (bindingSignature != null) {
KeyFlags flags = SignatureSubpacketsUtil.getKeyFlags(bindingSignature);
if (flags != null) {
return KeyFlag.fromBitmask(flags.getFlags());
}
@ -334,7 +318,7 @@ public class KeyRingInfo {
return Collections.emptyList();
}
PGPSignature userIdCertification = mostRecentUserIdSignatures.get(userId);
PGPSignature userIdCertification = getCurrentUserIdCertification(userId);
if (userIdCertification == null) {
return Collections.emptyList();
}
@ -377,12 +361,14 @@ public class KeyRingInfo {
private PGPSignature getMostRecentSignature() {
Set<PGPSignature> allSignatures = new HashSet<>();
PGPSignature mostRecentSelfSignature = getCurrentDirectKeySelfSignature();
PGPSignature revocationSelfSignature = getRevocationSelfSignature();
if (mostRecentSelfSignature != null) allSignatures.add(mostRecentSelfSignature);
if (revocationSelfSignature != null) allSignatures.add(revocationSelfSignature);
allSignatures.addAll(mostRecentUserIdSignatures.values());
allSignatures.addAll(mostRecentUserIdRevocations.values());
allSignatures.addAll(mostRecentSubkeyBindings.values());
allSignatures.addAll(mostRecentSubkeyRevocations.values());
allSignatures.addAll(signatures.userIdCertifications.values());
allSignatures.addAll(signatures.userIdRevocations.values());
allSignatures.addAll(signatures.subkeyBindings.values());
allSignatures.addAll(signatures.subkeyRevocations.values());
PGPSignature mostRecent = null;
for (PGPSignature signature : allSignatures) {
@ -399,7 +385,7 @@ public class KeyRingInfo {
* @return revocation date or null
*/
public Date getRevocationDate() {
return revocationSelfSignature == null ? null : revocationSelfSignature.getCreationTime();
return getRevocationSelfSignature() == null ? null : getRevocationSelfSignature().getCreationTime();
}
/**
@ -409,8 +395,8 @@ public class KeyRingInfo {
*/
public Date getPrimaryKeyExpirationDate() {
Date lastExpiration = null;
if (mostRecentSelfSignature != null) {
lastExpiration = SignatureUtils.getKeyExpirationDate(getCreationDate(), mostRecentSelfSignature);
if (getCurrentDirectKeySelfSignature() != null) {
lastExpiration = SignatureUtils.getKeyExpirationDate(getCreationDate(), getCurrentDirectKeySelfSignature());
}
for (String userId : getValidUserIds()) {
@ -432,7 +418,7 @@ public class KeyRingInfo {
if (subkey == null) {
throw new IllegalArgumentException("No subkey with fingerprint " + fingerprint + " found.");
}
return SignatureUtils.getKeyExpirationDate(subkey.getCreationTime(), mostRecentSubkeyBindings.get(fingerprint.getKeyId()));
return SignatureUtils.getKeyExpirationDate(subkey.getCreationTime(), getCurrentSubkeyBindingSignature(fingerprint.getKeyId()));
}
/**
@ -489,4 +475,130 @@ public class KeyRingInfo {
}
return false;
}
public PGPPublicKey getEncryptionSubkey(EncryptionStream.Purpose purpose) {
Iterator<PGPPublicKey> subkeys = keys.getPublicKeys();
while (subkeys.hasNext()) {
PGPPublicKey subKey = subkeys.next();
if (!isKeyValidlyBound(subKey.getKeyID())) {
continue;
}
if (!subKey.isEncryptionKey()) {
continue;
}
List<KeyFlag> keyFlags = getKeyFlagsOf(subKey.getKeyID());
switch (purpose) {
case COMMUNICATIONS:
if (keyFlags.contains(KeyFlag.ENCRYPT_COMMS)) {
return subKey;
}
break;
case STORAGE:
if (keyFlags.contains(KeyFlag.ENCRYPT_STORAGE)) {
return subKey;
}
break;
case STORAGE_AND_COMMUNICATIONS:
if (keyFlags.contains(KeyFlag.ENCRYPT_COMMS) || keyFlags.contains(KeyFlag.ENCRYPT_STORAGE)) {
return subKey;
}
break;
}
}
return null;
}
public PGPPublicKey getEncryptionSubkey(String userId, EncryptionStream.Purpose purpose) {
if (userId != null) {
if (!isUserIdValid(userId)) {
throw new KeyValidationException(userId, getCurrentUserIdCertification(userId), getUserIdRevocation(userId));
}
}
return getEncryptionSubkey(purpose);
}
public PGPPublicKey getSigningSubkey() {
Iterator<PGPPublicKey> subkeys = keys.getPublicKeys();
while (subkeys.hasNext()) {
PGPPublicKey subKey = subkeys.next();
if (!isKeyValidlyBound(subKey.getKeyID())) {
continue;
}
if (!subKey.isEncryptionKey()) {
continue;
}
List<KeyFlag> keyFlags = getKeyFlagsOf(subKey.getKeyID());
if (keyFlags.contains(KeyFlag.SIGN_DATA)) {
return subKey;
}
}
return null;
}
public List<HashAlgorithm> getPreferredHashAlgorithms(String userId, long keyID) {
PGPSignature signature = getCurrentUserIdCertification(userId == null ? getPrimaryUserId() : userId);
if (signature == null) {
signature = getCurrentDirectKeySelfSignature();
}
if (signature == null) {
signature = getCurrentSubkeyBindingSignature(keyID);
}
if (signature == null) {
throw new IllegalStateException("No valid signature.");
}
return SignatureSubpacketsUtil.parsePreferredHashAlgorithms(signature);
}
public static class Signatures {
private final PGPSignature primaryKeyRevocation;
private final PGPSignature primaryKeySelfSignature;
private final Map<String, PGPSignature> userIdRevocations;
private final Map<String, PGPSignature> userIdCertifications;
private final Map<Long, PGPSignature> subkeyRevocations;
private final Map<Long, PGPSignature> subkeyBindings;
public Signatures(PGPKeyRing keyRing, Date evaluationDate, Policy policy) {
primaryKeyRevocation = SignaturePicker.pickCurrentRevocationSelfSignature(keyRing, evaluationDate);
primaryKeySelfSignature = SignaturePicker.pickCurrentDirectKeySelfSignature(keyRing, evaluationDate);
userIdRevocations = new HashMap<>();
userIdCertifications = new HashMap<>();
subkeyRevocations = new HashMap<>();
subkeyBindings = new HashMap<>();
for (Iterator<String> it = keyRing.getPublicKey().getUserIDs(); it.hasNext(); ) {
String userId = it.next();
PGPSignature revocation = SignaturePicker.pickCurrentUserIdRevocationSignature(keyRing, userId, evaluationDate);
if (revocation != null) {
userIdRevocations.put(userId, revocation);
}
PGPSignature certification = SignaturePicker.pickCurrentUserIdCertificationSignature(keyRing, userId, evaluationDate);
if (certification != null) {
userIdCertifications.put(userId, certification);
}
}
Iterator<PGPPublicKey> keys = keyRing.getPublicKeys();
keys.next(); // Skip primary key
while (keys.hasNext()) {
PGPPublicKey subkey = keys.next();
PGPSignature subkeyRevocation = SignaturePicker.pickCurrentSubkeyBindingRevocationSignature(keyRing, subkey, evaluationDate);
if (subkeyRevocation != null) {
subkeyRevocations.put(subkey.getKeyID(), subkeyRevocation);
}
PGPSignature subkeyBinding = SignaturePicker.pickCurrentSubkeyBindingSignature(keyRing, subkey, evaluationDate);
if (subkeyBinding != null) {
subkeyBindings.put(subkey.getKeyID(), subkeyBinding);
}
}
}
}
}

View file

@ -19,6 +19,7 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.pgpainless.algorithm.CompressionAlgorithm;
import org.pgpainless.algorithm.HashAlgorithm;
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
import org.pgpainless.util.NotationRegistry;
@ -38,6 +39,8 @@ public final class Policy {
SymmetricKeyAlgorithmPolicy.defaultSymmetricKeyEncryptionAlgorithmPolicy();
private SymmetricKeyAlgorithmPolicy symmetricKeyDecryptionAlgorithmPolicy =
SymmetricKeyAlgorithmPolicy.defaultSymmetricKeyDecryptionAlgorithmPolicy();
private CompressionAlgorithmPolicy compressionAlgorithmPolicy =
CompressionAlgorithmPolicy.defaultCompressionAlgorithmPolicy();
private final NotationRegistry notationRegistry = new NotationRegistry();
private Policy() {
@ -142,6 +145,17 @@ public final class Policy {
this.symmetricKeyDecryptionAlgorithmPolicy = policy;
}
public CompressionAlgorithmPolicy getCompressionAlgorithmPolicy() {
return compressionAlgorithmPolicy;
}
public void setCompressionAlgorithmPolicy(CompressionAlgorithmPolicy policy) {
if (policy == null) {
throw new NullPointerException("Compression policy cannot be null.");
}
this.compressionAlgorithmPolicy = policy;
}
public static final class SymmetricKeyAlgorithmPolicy {
private final SymmetricKeyAlgorithm defaultSymmetricKeyAlgorithm;
@ -297,6 +311,39 @@ public final class Policy {
}
}
public static final class CompressionAlgorithmPolicy {
private final CompressionAlgorithm defaultCompressionAlgorithm;
private final List<CompressionAlgorithm> acceptableCompressionAlgorithms;
public CompressionAlgorithmPolicy(CompressionAlgorithm defaultCompressionAlgorithm,
List<CompressionAlgorithm> acceptableCompressionAlgorithms) {
this.defaultCompressionAlgorithm = defaultCompressionAlgorithm;
this.acceptableCompressionAlgorithms = Collections.unmodifiableList(acceptableCompressionAlgorithms);
}
public CompressionAlgorithm defaultCompressionAlgorithm() {
return defaultCompressionAlgorithm;
}
public boolean isAcceptable(int compressionAlgorithmTag) {
return isAcceptable(CompressionAlgorithm.fromId(compressionAlgorithmTag));
}
public boolean isAcceptable(CompressionAlgorithm compressionAlgorithm) {
return acceptableCompressionAlgorithms.contains(compressionAlgorithm);
}
public static CompressionAlgorithmPolicy defaultCompressionAlgorithmPolicy() {
return new CompressionAlgorithmPolicy(CompressionAlgorithm.UNCOMPRESSED, Arrays.asList(
CompressionAlgorithm.UNCOMPRESSED,
CompressionAlgorithm.ZIP,
CompressionAlgorithm.BZIP2,
CompressionAlgorithm.ZLIB
));
}
}
/**
* Return the {@link NotationRegistry} of PGPainless.
* The notation registry is used to decide, whether or not a Notation is known or not.

View file

@ -59,7 +59,7 @@ public abstract class SelectSignatureFromKey {
* Criterion that checks if the signature is valid at the validation date.
* A signature is not valid if it was created after the validation date, or if it is expired at the validation date.
*
* creationTime less than or equal validationDate less than expirationDate.
* creationTime &le; validationDate &lt; expirationDate.
*
* @param validationDate validation date
* @return criterion implementation

View file

@ -25,8 +25,11 @@ import org.bouncycastle.bcpg.sig.SignatureCreationTime;
import org.bouncycastle.openpgp.PGPKeyRing;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPSignature;
import org.pgpainless.PGPainless;
import org.pgpainless.algorithm.SignatureType;
import org.pgpainless.exception.SignatureValidationException;
import org.pgpainless.key.util.RevocationAttributes;
import org.pgpainless.policy.Policy;
import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil;
import org.pgpainless.util.CollectionUtils;
@ -53,36 +56,19 @@ public class SignaturePicker {
* @return most recent, valid key revocation signature
*/
public static PGPSignature pickCurrentRevocationSelfSignature(PGPKeyRing keyRing, Date validationDate) {
Policy policy = PGPainless.getPolicy();
PGPPublicKey primaryKey = keyRing.getPublicKey();
List<PGPSignature> signatures = getSortedSignaturesOfType(primaryKey, SignatureType.KEY_REVOCATION);
PGPSignature mostCurrentValidSig = null;
for (PGPSignature signature : signatures) {
if (!SelectSignatureFromKey.isWellFormed().accept(signature, primaryKey, keyRing)) {
// Signature is not well-formed. Reject
try {
SignatureValidator.verifyKeyRevocationSignature(signature, primaryKey, policy, validationDate);
} catch (SignatureValidationException e) {
// Signature is not valid
continue;
}
if (!SelectSignatureFromKey.isCreatedBy(keyRing.getPublicKey()).accept(signature, primaryKey, keyRing)) {
// Revocation signature was not created by primary key
continue;
}
RevocationReason reason = SignatureSubpacketsUtil.getRevocationReason(signature);
if (reason != null && !RevocationAttributes.Reason.isHardRevocation(reason.getRevocationReason())) {
// reason code states soft revocation
if (!SelectSignatureFromKey.isValidAt(validationDate).accept(signature, primaryKey, keyRing)) {
// Soft revocation is either expired or not yet valid
continue;
}
}
if (!SelectSignatureFromKey.isValidKeyRevocationSignature(primaryKey).accept(signature, primaryKey, keyRing)) {
// sig does not check out
continue;
}
mostCurrentValidSig = signature;
}

View file

@ -20,6 +20,8 @@ import java.util.Arrays;
import java.util.Date;
import java.util.List;
import javax.annotation.Nullable;
import org.bouncycastle.bcpg.sig.Exportable;
import org.bouncycastle.bcpg.sig.Features;
import org.bouncycastle.bcpg.sig.IntendedRecipientFingerprint;
@ -44,7 +46,11 @@ import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.openpgp.PGPSignatureList;
import org.bouncycastle.openpgp.PGPSignatureSubpacketVector;
import org.bouncycastle.util.encoders.Hex;
import org.pgpainless.algorithm.CompressionAlgorithm;
import org.pgpainless.algorithm.HashAlgorithm;
import org.pgpainless.algorithm.KeyFlag;
import org.pgpainless.algorithm.SignatureSubpacket;
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
import org.pgpainless.key.OpenPgpV4Fingerprint;
import org.pgpainless.signature.SignatureUtils;
@ -200,6 +206,17 @@ public class SignatureSubpacketsUtil {
return hashed(signature, SignatureSubpacket.preferredSymmetricAlgorithms);
}
public static List<SymmetricKeyAlgorithm> parsePreferredSymmetricKeyAlgorithms(PGPSignature signature) {
List<SymmetricKeyAlgorithm> algorithms = new ArrayList<>();
PreferredAlgorithms preferences = getPreferredSymmetricAlgorithms(signature);
if (preferences != null) {
for (int code : preferences.getPreferences()) {
algorithms.add(SymmetricKeyAlgorithm.fromId(code));
}
}
return algorithms;
}
/**
* Return the hash algorithm preferences from the signatures hashed area.
*
@ -210,6 +227,17 @@ public class SignatureSubpacketsUtil {
return hashed(signature, SignatureSubpacket.preferredHashAlgorithms);
}
public static List<HashAlgorithm> parsePreferredHashAlgorithms(PGPSignature signature) {
List<HashAlgorithm> algorithms = new ArrayList<>();
PreferredAlgorithms preferences = getPreferredHashAlgorithms(signature);
if (preferences != null) {
for (int code : preferences.getPreferences()) {
algorithms.add(HashAlgorithm.fromId(code));
}
}
return algorithms;
}
/**
* Return the compression algorithm preferences from the signatures hashed area.
*
@ -220,6 +248,17 @@ public class SignatureSubpacketsUtil {
return hashed(signature, SignatureSubpacket.preferredCompressionAlgorithms);
}
public static List<CompressionAlgorithm> parsePreferredCompressionAlgorithms(PGPSignature signature) {
List<CompressionAlgorithm> algorithms = new ArrayList<>();
PreferredAlgorithms preferences = getPreferredCompressionAlgorithms(signature);
if (preferences != null) {
for (int code : preferences.getPreferences()) {
algorithms.add(CompressionAlgorithm.fromId(code));
}
}
return algorithms;
}
/**
* Return the primary user-id subpacket from the signatures hashed area.
*
@ -240,6 +279,24 @@ public class SignatureSubpacketsUtil {
return hashed(signature, SignatureSubpacket.keyFlags);
}
/**
* Return a list of key flags carried by the signature.
* If the signature is null, or has no {@link KeyFlags} subpacket, return null.
*
* @param signature signature
* @return list of key flags
*/
public static List<KeyFlag> parseKeyFlags(@Nullable PGPSignature signature) {
if (signature == null) {
return null;
}
KeyFlags keyFlags = getKeyFlags(signature);
if (keyFlags == null) {
return null;
}
return KeyFlag.fromBitmask(keyFlags.getFlags());
}
/**
* Return the features subpacket from the signatures hashed area.
*

View file

@ -17,18 +17,23 @@ package org.bouncycastle;
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.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSignature;
import org.junit.jupiter.api.Test;
import org.pgpainless.PGPainless;
import org.pgpainless.key.util.KeyRingUtils;
import org.pgpainless.util.CollectionUtils;
public class PGPPublicKeyRingTest {
@ -57,4 +62,21 @@ public class PGPPublicKeyRingTest {
}
}
}
@Test
public void removeUserIdTest() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException {
String userId = "alice@wonderland.lit";
PGPSecretKeyRing secretKeyRing = PGPainless.generateKeyRing().simpleEcKeyRing(userId);
PGPPublicKeyRing publicKeys = KeyRingUtils.publicKeyRingFrom(secretKeyRing);
List<String> userIds = CollectionUtils.iteratorToList(publicKeys.getPublicKey().getUserIDs());
assertTrue(userIds.contains(userId));
PGPPublicKey publicKey = publicKeys.getPublicKey();
PGPSignature cert = publicKey.getSignaturesForID(userId).next();
publicKey = PGPPublicKey.removeCertification(publicKey, cert);
userIds = CollectionUtils.iteratorToList(publicKey.getUserIDs());
assertFalse(userIds.contains(userId));
}
}

View file

@ -41,12 +41,13 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.pgpainless.PGPainless;
import org.pgpainless.algorithm.DocumentSignatureType;
import org.pgpainless.algorithm.KeyFlag;
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
import org.pgpainless.decryption_verification.DecryptionStream;
import org.pgpainless.decryption_verification.OpenPgpMetadata;
import org.pgpainless.implementation.ImplementationFactory;
import org.pgpainless.key.OpenPgpV4Fingerprint;
import org.pgpainless.key.SubkeyIdentifier;
import org.pgpainless.key.TestKeys;
import org.pgpainless.key.generation.KeySpec;
import org.pgpainless.key.generation.type.KeyType;
@ -155,26 +156,20 @@ public class EncryptDecryptTest {
EncryptionStream encryptor = PGPainless.encryptAndOrSign()
.onOutputStream(envelope)
.toRecipients(recipientPub)
.usingSecureAlgorithms()
.signWith(keyDecryptor, senderSec)
.signBinaryDocument()
.toRecipient(recipientPub)
.and()
.signInlineWith(keyDecryptor, senderSec, null, DocumentSignatureType.BINARY_DOCUMENT)
.noArmor();
Streams.pipeAll(new ByteArrayInputStream(secretMessage), encryptor);
encryptor.close();
byte[] encryptedSecretMessage = envelope.toByteArray();
OpenPgpMetadata encryptionResult = encryptor.getResult();
EncryptionResult encryptionResult = encryptor.getResult();
assertFalse(encryptionResult.getSignatures().isEmpty());
for (OpenPgpV4Fingerprint fingerprint : encryptionResult.getVerifiedSignatures().keySet()) {
assertTrue(BCUtil.keyRingContainsKeyWithId(senderPub, fingerprint.getKeyId()));
}
assertFalse(encryptionResult.getRecipientKeyIds().isEmpty());
for (long keyId : encryptionResult.getRecipientKeyIds()) {
assertTrue(BCUtil.keyRingContainsKeyWithId(recipientPub, keyId));
assertFalse(encryptionResult.getRecipients().isEmpty());
for (SubkeyIdentifier encryptionKey : encryptionResult.getRecipients()) {
assertTrue(BCUtil.keyRingContainsKeyWithId(recipientPub, encryptionKey.getKeyId()));
}
assertEquals(SymmetricKeyAlgorithm.AES_256, encryptionResult.getSymmetricKeyAlgorithm());
@ -214,15 +209,14 @@ public class EncryptDecryptTest {
ByteArrayOutputStream dummyOut = new ByteArrayOutputStream();
EncryptionStream signer = PGPainless.encryptAndOrSign().onOutputStream(dummyOut)
.doNotEncrypt()
.createDetachedSignature()
.signWith(keyRingProtector, signingKeys)
.signBinaryDocument()
.signDetachedWith(keyRingProtector, signingKeys)
.noArmor();
Streams.pipeAll(inputStream, signer);
signer.close();
OpenPgpMetadata metadata = signer.getResult();
Set<PGPSignature> signatureSet = metadata.getSignatures();
EncryptionResult metadata = signer.getResult();
Set<PGPSignature> signatureSet = metadata.getDetachedSignatures().get(metadata.getDetachedSignatures().keySet().iterator().next());
ByteArrayOutputStream sigOut = new ByteArrayOutputStream();
ArmoredOutputStream armorOut = ArmoredOutputStreamFactory.get(sigOut);
signatureSet.iterator().next().encode(armorOut);
@ -244,8 +238,8 @@ public class EncryptDecryptTest {
Streams.pipeAll(verifier, dummyOut);
verifier.close();
metadata = verifier.getResult();
assertFalse(metadata.getVerifiedSignatures().isEmpty());
OpenPgpMetadata decryptionResult = verifier.getResult();
assertFalse(decryptionResult.getVerifiedSignatures().isEmpty());
}
@ParameterizedTest
@ -259,8 +253,7 @@ public class EncryptDecryptTest {
ByteArrayOutputStream signOut = new ByteArrayOutputStream();
EncryptionStream signer = PGPainless.encryptAndOrSign().onOutputStream(signOut)
.doNotEncrypt()
.signWith(keyRingProtector, signingKeys)
.signBinaryDocument()
.signInlineWith(keyRingProtector, signingKeys)
.asciiArmor();
Streams.pipeAll(inputStream, signer);
signer.close();
@ -344,6 +337,6 @@ public class EncryptDecryptTest {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
assertThrows(IllegalArgumentException.class, () ->
PGPainless.encryptAndOrSign().onOutputStream(outputStream)
.toRecipients(publicKeys));
.toRecipient(publicKeys));
}
}

View file

@ -37,8 +37,8 @@ public class EncryptionStreamClosedTest {
OutputStream out = new ByteArrayOutputStream();
EncryptionStream stream = PGPainless.encryptAndOrSign()
.onOutputStream(out)
.forPassphrases(Passphrase.fromPassword("dummy"))
.usingSecureAlgorithms()
.forPassphrase(Passphrase.fromPassword("dummy"))
.and()
.doNotSign()
.asciiArmor();

View file

@ -69,8 +69,8 @@ public class FileInfoTest {
ByteArrayOutputStream dataOut = new ByteArrayOutputStream();
EncryptionStream encryptionStream = PGPainless.encryptAndOrSign()
.onOutputStream(dataOut, fileInfo)
.toRecipients(publicKeys)
.usingSecureAlgorithms()
.toRecipient(publicKeys)
.and()
.doNotSign()
.noArmor();

View file

@ -31,6 +31,7 @@ import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.util.io.Streams;
import org.pgpainless.PGPainless;
import org.pgpainless.algorithm.DocumentSignatureType;
import org.pgpainless.key.TestKeys;
import org.pgpainless.key.generation.type.rsa.RsaLength;
import org.pgpainless.key.protection.SecretKeyRingProtector;
@ -111,11 +112,9 @@ public class LengthTest {
OutputStream encryptor = PGPainless.encryptAndOrSign()
.onOutputStream(envelope)
.toRecipients(recipientPub)
// .doNotEncrypt()
.usingSecureAlgorithms()
.signWith(keyDecryptor, senderSec)
.signBinaryDocument()
.toRecipient(recipientPub)
.and()
.signInlineWith(keyDecryptor, senderSec, "simplejid@server.tld", DocumentSignatureType.BINARY_DOCUMENT)
.noArmor();
Streams.pipeAll(new ByteArrayInputStream(secretMessage), encryptor);

View file

@ -37,7 +37,7 @@ import org.bouncycastle.util.io.Streams;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.pgpainless.PGPainless;
import org.pgpainless.algorithm.CompressionAlgorithm;
import org.pgpainless.algorithm.DocumentSignatureType;
import org.pgpainless.algorithm.HashAlgorithm;
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
import org.pgpainless.decryption_verification.DecryptionStream;
@ -70,10 +70,11 @@ public class SigningTest {
EncryptionStream encryptionStream = PGPainless.encryptAndOrSign(EncryptionStream.Purpose.STORAGE)
.onOutputStream(out)
.toRecipients(keys)
.andToSelf(KeyRingUtils.publicKeyRingFrom(cryptieKeys))
.usingAlgorithms(SymmetricKeyAlgorithm.AES_192, HashAlgorithm.SHA384, CompressionAlgorithm.ZIP)
.signWith(SecretKeyRingProtector.unlockSingleKeyWith(TestKeys.CRYPTIE_PASSPHRASE, cryptieSigningKey), cryptieKeys)
.signCanonicalText()
.and()
.toRecipient(KeyRingUtils.publicKeyRingFrom(cryptieKeys))
.and()
.signInlineWith(SecretKeyRingProtector.unlockSingleKeyWith(TestKeys.CRYPTIE_PASSPHRASE, cryptieSigningKey),
cryptieKeys, TestKeys.CRYPTIE_UID, DocumentSignatureType.CANONICAL_TEXT_DOCUMENT)
.asciiArmor();
byte[] messageBytes = "This message is signed and encrypted to Romeo and Juliet.".getBytes(StandardCharsets.UTF_8);

View file

@ -193,8 +193,7 @@ public class ChangeSecretKeyRingPassphraseTest {
ByteArrayOutputStream dummy = new ByteArrayOutputStream();
EncryptionStream stream = PGPainless.encryptAndOrSign().onOutputStream(dummy)
.doNotEncrypt()
.signWith(PasswordBasedSecretKeyRingProtector.forKey(keyRing, passphrase), keyRing)
.signBinaryDocument()
.signInlineWith(PasswordBasedSecretKeyRingProtector.forKey(keyRing, passphrase), keyRing)
.noArmor();
Streams.pipeAll(new ByteArrayInputStream(dummyMessage.getBytes()), stream);

View file

@ -36,7 +36,7 @@ public class MultiPassphraseSymmetricEncryptionTest {
@ParameterizedTest
@MethodSource("org.pgpainless.util.TestUtil#provideImplementationFactories")
@Disabled
public void test(ImplementationFactory implementationFactory) throws IOException, PGPException {
public void encryptDecryptWithMultiplePassphrases(ImplementationFactory implementationFactory) throws IOException, PGPException {
ImplementationFactory.setFactoryImplementation(implementationFactory);
String message = "Here we test if during decryption of a message that was encrypted with two passphrases, " +
"the decryptor finds the session key encrypted for the right passphrase.";
@ -44,8 +44,10 @@ public class MultiPassphraseSymmetricEncryptionTest {
ByteArrayOutputStream ciphertextOut = new ByteArrayOutputStream();
EncryptionStream encryptor = PGPainless.encryptAndOrSign()
.onOutputStream(ciphertextOut)
.forPassphrases(Passphrase.fromPassword("p1"), Passphrase.fromPassword("p2"))
.usingSecureAlgorithms()
.forPassphrase(Passphrase.fromPassword("p1"))
.and()
.forPassphrase(Passphrase.fromPassword("p2"))
.and()
.doNotSign()
.noArmor();

View file

@ -30,6 +30,7 @@ import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.pgpainless.PGPainless;
import org.pgpainless.decryption_verification.DecryptionStream;
import org.pgpainless.encryption_signing.EncryptionBuilderInterface;
import org.pgpainless.encryption_signing.EncryptionStream;
import org.pgpainless.implementation.ImplementationFactory;
import org.pgpainless.key.TestKeys;
@ -46,7 +47,7 @@ public class SymmetricEncryptionTest {
@ParameterizedTest
@MethodSource("org.pgpainless.util.TestUtil#provideImplementationFactories")
public void test(ImplementationFactory implementationFactory) throws IOException, PGPException {
public void encryptWithKeyAndPassphrase_DecryptWithKey(ImplementationFactory implementationFactory) throws IOException, PGPException {
ImplementationFactory.setFactoryImplementation(implementationFactory);
byte[] plaintext = "This is a secret message".getBytes(StandardCharsets.UTF_8);
ByteArrayInputStream plaintextIn = new ByteArrayInputStream(plaintext);
@ -54,12 +55,13 @@ public class SymmetricEncryptionTest {
Passphrase encryptionPassphrase = Passphrase.fromPassword("greenBeans");
ByteArrayOutputStream ciphertextOut = new ByteArrayOutputStream();
EncryptionStream encryptor = PGPainless.encryptAndOrSign().onOutputStream(ciphertextOut)
.forPassphrases(encryptionPassphrase)
EncryptionBuilderInterface.Armor armor = PGPainless.encryptAndOrSign().onOutputStream(ciphertextOut)
.forPassphrase(encryptionPassphrase)
.and()
.toRecipients(encryptionKey)
.usingSecureAlgorithms()
.doNotSign()
.toRecipient(encryptionKey)
.and()
.doNotSign();
EncryptionStream encryptor = armor
.noArmor();
Streams.pipeAll(plaintextIn, encryptor);

View file

@ -56,6 +56,6 @@ public class TestEncryptCommsStorageFlagsDifferentiated {
.onOutputStream(out);
// since the key does not carry the flag ENCRYPT_COMMS, it cannot be used by the stream.
assertThrows(IllegalArgumentException.class, () -> builder.toRecipients(publicKeys));
assertThrows(IllegalArgumentException.class, () -> builder.toRecipient(publicKeys));
}
}

View file

@ -28,7 +28,7 @@ import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.util.io.Streams;
import org.junit.jupiter.api.Test;
import org.pgpainless.PGPainless;
import org.pgpainless.decryption_verification.OpenPgpMetadata;
import org.pgpainless.encryption_signing.EncryptionResult;
import org.pgpainless.encryption_signing.EncryptionStream;
import org.pgpainless.key.WeirdKeys;
import org.pgpainless.key.util.KeyRingUtils;
@ -57,16 +57,16 @@ public class TestTwoSubkeysEncryption {
ByteArrayOutputStream out = new ByteArrayOutputStream();
EncryptionStream encryptionStream = PGPainless.encryptAndOrSign(EncryptionStream.Purpose.STORAGE)
.onOutputStream(out)
.toRecipients(publicKeys)
.usingSecureAlgorithms()
.toRecipient(publicKeys)
.and()
.doNotSign()
.noArmor();
Streams.pipeAll(getPlainIn(), encryptionStream);
encryptionStream.close();
OpenPgpMetadata metadata = encryptionStream.getResult();
EncryptionResult metadata = encryptionStream.getResult();
assertEquals(2, metadata.getRecipientKeyIds().size());
assertEquals(2, metadata.getRecipients().size());
}
}