mirror of
https://github.com/pgpainless/pgpainless.git
synced 2024-12-23 03:17:58 +01:00
Reworking encryption/decryption API.
This commit is contained in:
parent
7e2c89b1b3
commit
89a0adddd8
29 changed files with 1454 additions and 631 deletions
|
@ -11,7 +11,7 @@ About
|
||||||
|
|
||||||
PGPainless aims to make using OpenPGP in Java projects as simple as possible.
|
PGPainless aims to make using OpenPGP in Java projects as simple as possible.
|
||||||
It does so by introducing an intuitive Builder structure, which allows easy
|
It does so by introducing an intuitive Builder structure, which allows easy
|
||||||
setup of encryption / decrytion operations, as well as straight forward key generation.
|
setup of encryptionOptions / decrytion operations, as well as straight forward key generation.
|
||||||
|
|
||||||
PGPainless is based around the Bouncycastle java library and can be used on Android down to API level 10.
|
PGPainless is based around the Bouncycastle java library and can be used on Android down to API level 10.
|
||||||
|
|
||||||
|
@ -74,7 +74,7 @@ Take for example a look at this delicious key:
|
||||||
|
|
||||||
### Encrypt / Sign Data
|
### Encrypt / Sign Data
|
||||||
|
|
||||||
Encrypting and signing data is pretty straight forward as well.
|
Encrypting and signingOptions data is pretty straight forward as well.
|
||||||
```java
|
```java
|
||||||
EncryptionStream encryptor = PGPainless.encryptAndOrSign()
|
EncryptionStream encryptor = PGPainless.encryptAndOrSign()
|
||||||
.onOutputStream(targetOuputStream)
|
.onOutputStream(targetOuputStream)
|
||||||
|
@ -102,7 +102,7 @@ Additionally you can get information about the encrypted data by calling
|
||||||
OpenPgpMetadata result = encryptor.getResult();
|
OpenPgpMetadata result = encryptor.getResult();
|
||||||
```
|
```
|
||||||
|
|
||||||
This object will contain information like to which keys the message is encrypted, which keys were used for signing and so on.
|
This object will contain information like to which keys the message is encrypted, which keys were used for signingOptions and so on.
|
||||||
|
|
||||||
### Decrypt / Verify Encrypted Data
|
### Decrypt / Verify Encrypted Data
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,311 +17,202 @@ package org.pgpainless.encryption_signing;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
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 javax.annotation.Nonnull;
|
||||||
|
|
||||||
import org.bouncycastle.openpgp.PGPException;
|
import org.bouncycastle.openpgp.PGPException;
|
||||||
import org.bouncycastle.openpgp.PGPPrivateKey;
|
|
||||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
|
||||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||||
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
||||||
import org.bouncycastle.openpgp.PGPSecretKey;
|
|
||||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||||
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
|
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
|
||||||
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
|
|
||||||
import org.pgpainless.PGPainless;
|
import org.pgpainless.PGPainless;
|
||||||
import org.pgpainless.algorithm.CompressionAlgorithm;
|
import org.pgpainless.algorithm.CompressionAlgorithm;
|
||||||
|
import org.pgpainless.algorithm.DocumentSignatureType;
|
||||||
import org.pgpainless.algorithm.HashAlgorithm;
|
import org.pgpainless.algorithm.HashAlgorithm;
|
||||||
import org.pgpainless.algorithm.KeyFlag;
|
|
||||||
import org.pgpainless.algorithm.SignatureType;
|
|
||||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
||||||
import org.pgpainless.decryption_verification.OpenPgpMetadata;
|
import org.pgpainless.decryption_verification.OpenPgpMetadata;
|
||||||
import org.pgpainless.key.KeyRingValidator;
|
import org.pgpainless.exception.KeyValidationException;
|
||||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
|
||||||
import org.pgpainless.key.SubkeyIdentifier;
|
|
||||||
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
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.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 {
|
public class EncryptionBuilder implements EncryptionBuilderInterface {
|
||||||
|
|
||||||
private final EncryptionStream.Purpose purpose;
|
|
||||||
private OutputStream outputStream;
|
private OutputStream outputStream;
|
||||||
private final Map<SubkeyIdentifier, PGPPublicKeyRing> encryptionKeys = new ConcurrentHashMap<>();
|
private EncryptionOptions encryptionOptions;
|
||||||
private final Set<Passphrase> encryptionPassphrases = new HashSet<>();
|
private SigningOptions signingOptions = new SigningOptions();
|
||||||
private boolean detachedSignature = false;
|
private ProducerOptions options;
|
||||||
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 OpenPgpMetadata.FileInfo fileInfo;
|
private OpenPgpMetadata.FileInfo fileInfo;
|
||||||
|
|
||||||
public EncryptionBuilder() {
|
public EncryptionBuilder() {
|
||||||
this.purpose = EncryptionStream.Purpose.COMMUNICATIONS;
|
this.encryptionOptions = new EncryptionOptions(EncryptionStream.Purpose.COMMUNICATIONS);
|
||||||
}
|
}
|
||||||
|
|
||||||
public EncryptionBuilder(@Nonnull EncryptionStream.Purpose purpose) {
|
public EncryptionBuilder(@Nonnull EncryptionStream.Purpose purpose) {
|
||||||
this.purpose = purpose;
|
this.encryptionOptions = new EncryptionOptions(purpose);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ToRecipients onOutputStream(@Nonnull OutputStream outputStream, OpenPgpMetadata.FileInfo fileInfo) {
|
public ToRecipientsOrNoEncryption onOutputStream(@Nonnull OutputStream outputStream, OpenPgpMetadata.FileInfo fileInfo) {
|
||||||
this.outputStream = outputStream;
|
this.outputStream = outputStream;
|
||||||
this.fileInfo = fileInfo;
|
this.fileInfo = fileInfo;
|
||||||
return new ToRecipientsImpl();
|
return new ToRecipientsOrNoEncryptionImpl();
|
||||||
}
|
}
|
||||||
|
|
||||||
class ToRecipientsImpl implements ToRecipients {
|
class ToRecipientsImpl implements ToRecipients {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public WithAlgorithms toRecipients(@Nonnull PGPPublicKeyRing... keys) {
|
public AdditionalRecipients toRecipient(@Nonnull PGPPublicKeyRing key) {
|
||||||
if (keys.length == 0) {
|
encryptionOptions.addRecipient(key);
|
||||||
throw new IllegalArgumentException("No public keys provided.");
|
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) {
|
for (PGPPublicKeyRing ring : keys) {
|
||||||
PGPPublicKeyRing validatedKeyRing = KeyRingValidator.validate(ring, PGPainless.getPolicy());
|
encryptionOptions.addRecipient(ring, userId);
|
||||||
for (PGPPublicKey k : validatedKeyRing) {
|
|
||||||
if (encryptionKeySelector().accept(k)) {
|
|
||||||
encryptionKeys.put(new SubkeyIdentifier(ring, k.getKeyID()), ring);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (encryptionKeys.isEmpty()) {
|
return new AdditionalRecipientsImpl();
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public WithAlgorithms toRecipients(@Nonnull PGPPublicKeyRingCollection... keys) {
|
public AdditionalRecipients toRecipients(@Nonnull PGPPublicKeyRingCollection keys) {
|
||||||
if (keys.length == 0) {
|
for (PGPPublicKeyRing ring : keys) {
|
||||||
throw new IllegalArgumentException("No key ring collections provided.");
|
encryptionOptions.addRecipient(ring);
|
||||||
}
|
}
|
||||||
|
return new AdditionalRecipientsImpl();
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public WithAlgorithms forPassphrases(Passphrase... passphrases) {
|
public AdditionalRecipients forPassphrase(Passphrase passphrase) {
|
||||||
List<Passphrase> passphraseList = new ArrayList<>();
|
encryptionOptions.addPassphrase(passphrase);
|
||||||
for (Passphrase passphrase : passphrases) {
|
return new AdditionalRecipientsImpl();
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class WithAlgorithmsImpl implements WithAlgorithms {
|
class ToRecipientsOrNoEncryptionImpl extends ToRecipientsImpl implements ToRecipientsOrNoEncryption {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public WithAlgorithms andToSelf(@Nonnull PGPPublicKeyRing... keys) {
|
public EncryptionStream withOptions(ProducerOptions options) throws PGPException, IOException {
|
||||||
if (keys.length == 0) {
|
if (options == null) {
|
||||||
throw new IllegalArgumentException("Recipient list MUST NOT be empty.");
|
throw new NullPointerException("ProducerOptions cannot be null.");
|
||||||
}
|
}
|
||||||
for (PGPPublicKeyRing ring : keys) {
|
return new EncryptionStream(outputStream, options, fileInfo);
|
||||||
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
|
@Override
|
||||||
public WithAlgorithms andToSelf(@Nonnull PGPPublicKeyRingCollection keys) {
|
public SignWithOrDontSign doNotEncrypt() {
|
||||||
for (PGPPublicKeyRing ring : keys) {
|
EncryptionBuilder.this.encryptionOptions = null;
|
||||||
Map<SubkeyIdentifier, PGPPublicKeyRing> encryptionKeys = new ConcurrentHashMap<>();
|
return new SignWithOrDontSignImpl();
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DetachedSignImpl implements DetachedSign {
|
class AdditionalRecipientsImpl implements AdditionalRecipients {
|
||||||
|
@Override
|
||||||
|
public ToRecipientsOrSign and() {
|
||||||
|
return new ToRecipientsOrSignImpl();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ToRecipientsOrSignImpl extends ToRecipientsImpl implements ToRecipientsOrSign {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SignWith createDetachedSignature() {
|
public Armor doNotSign() {
|
||||||
EncryptionBuilder.this.detachedSignature = true;
|
EncryptionBuilder.this.signingOptions = null;
|
||||||
return new SignWithImpl();
|
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
|
@Override
|
||||||
public Armor doNotSign() {
|
public Armor doNotSign() {
|
||||||
return new ArmorImpl();
|
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 {
|
class SignWithImpl implements SignWith {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DocumentType signWith(@Nonnull SecretKeyRingProtector decryptor,
|
public AdditionalSignWith signWith(@Nonnull SecretKeyRingProtector decryptor,
|
||||||
@Nonnull PGPSecretKeyRing... keyRings) {
|
@Nonnull PGPSecretKeyRing... keyRings)
|
||||||
if (keyRings.length == 0) {
|
throws KeyValidationException {
|
||||||
throw new IllegalArgumentException("Signing key list MUST NOT be empty.");
|
for (PGPSecretKeyRing secretKeyRing : keyRings) {
|
||||||
|
signingOptions.addInlineSignature(decryptor, secretKeyRing, DocumentSignatureType.BINARY_DOCUMENT);
|
||||||
}
|
}
|
||||||
for (PGPSecretKeyRing ring : keyRings) {
|
return new AdditionalSignWithImpl();
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DocumentType signWith(@Nonnull SecretKeyRingProtector decryptor, @Nonnull PGPSecretKeyRingCollection keyRings) {
|
public AdditionalSignWith signWith(@Nonnull SecretKeyRingProtector decryptor, @Nonnull PGPSecretKeyRingCollection keyRings)
|
||||||
Iterator<PGPSecretKeyRing> iterator = keyRings.iterator();
|
throws KeyValidationException {
|
||||||
if (!iterator.hasNext()) {
|
for (PGPSecretKeyRing key : keyRings) {
|
||||||
throw new IllegalArgumentException("Signing key collection MUST NOT be empty.");
|
signingOptions.addInlineSignature(decryptor, key, DocumentSignatureType.BINARY_DOCUMENT);
|
||||||
}
|
}
|
||||||
while (iterator.hasNext()) {
|
return new AdditionalSignWithImpl();
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (signingKeys.isEmpty()) {
|
@Override
|
||||||
throw new IllegalArgumentException("No suitable signing key found in the key ring " + new OpenPgpV4Fingerprint(ring));
|
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);
|
@Override
|
||||||
}
|
public AdditionalSignWith signDetachedWith(@Nonnull SecretKeyRingProtector secretKeyDecryptor,
|
||||||
|
@Nonnull PGPSecretKeyRing signingKey,
|
||||||
EncryptionBuilder.this.signingKeysDecryptor = decryptor;
|
String userId,
|
||||||
return new DocumentTypeImpl();
|
DocumentSignatureType signatureType)
|
||||||
|
throws PGPException, KeyValidationException {
|
||||||
|
signingOptions.addInlineSignature(secretKeyDecryptor, signingKey, userId, signatureType);
|
||||||
|
return new AdditionalSignWithImpl();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DocumentTypeImpl implements DocumentType {
|
class AdditionalSignWithImpl implements AdditionalSignWith {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Armor signBinaryDocument() {
|
public SignWith and() {
|
||||||
EncryptionBuilder.this.signatureType = SignatureType.BINARY_DOCUMENT;
|
return new SignWithImpl();
|
||||||
return new ArmorImpl();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Armor signCanonicalText() {
|
public EncryptionStream asciiArmor() throws IOException, PGPException {
|
||||||
EncryptionBuilder.this.signatureType = SignatureType.CANONICAL_TEXT_DOCUMENT;
|
return new ArmorImpl().asciiArmor();
|
||||||
return new ArmorImpl();
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EncryptionStream noArmor() throws IOException, PGPException {
|
||||||
|
return new ArmorImpl().noArmor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -329,70 +220,87 @@ public class EncryptionBuilder implements EncryptionBuilderInterface {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public EncryptionStream asciiArmor() throws IOException, PGPException {
|
public EncryptionStream asciiArmor() throws IOException, PGPException {
|
||||||
EncryptionBuilder.this.asciiArmor = true;
|
assignProducerOptions();
|
||||||
|
options.setAsciiArmor(true);
|
||||||
return build();
|
return build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public EncryptionStream noArmor() throws IOException, PGPException {
|
public EncryptionStream noArmor() throws IOException, PGPException {
|
||||||
EncryptionBuilder.this.asciiArmor = false;
|
assignProducerOptions();
|
||||||
|
options.setAsciiArmor(false);
|
||||||
return build();
|
return build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private EncryptionStream build() throws IOException, PGPException {
|
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(
|
return new EncryptionStream(
|
||||||
EncryptionBuilder.this.outputStream,
|
EncryptionBuilder.this.outputStream,
|
||||||
EncryptionBuilder.this.encryptionKeys,
|
EncryptionBuilder.this.options,
|
||||||
EncryptionBuilder.this.encryptionPassphrases,
|
|
||||||
EncryptionBuilder.this.detachedSignature,
|
|
||||||
signatureType,
|
|
||||||
privateKeys,
|
|
||||||
EncryptionBuilder.this.symmetricKeyAlgorithm,
|
|
||||||
EncryptionBuilder.this.hashAlgorithm,
|
|
||||||
EncryptionBuilder.this.compressionAlgorithm,
|
|
||||||
EncryptionBuilder.this.asciiArmor,
|
|
||||||
fileInfo);
|
fileInfo);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
PublicKeySelectionStrategy encryptionKeySelector() {
|
private void assignProducerOptions() {
|
||||||
KeyFlag[] flags = mapPurposeToKeyFlags(purpose);
|
if (encryptionOptions != null && signingOptions != null) {
|
||||||
return new And.PubKeySelectionStrategy(
|
options = ProducerOptions.signAndEncrypt(encryptionOptions, signingOptions);
|
||||||
new NoRevocation.PubKeySelectionStrategy(),
|
} else if (encryptionOptions != null) {
|
||||||
new EncryptionKeySelectionStrategy(flags));
|
options = ProducerOptions.encrypt(encryptionOptions);
|
||||||
}
|
} else if (signingOptions != null) {
|
||||||
|
options = ProducerOptions.sign(signingOptions);
|
||||||
SecretKeySelectionStrategy signingKeySelector() {
|
} else {
|
||||||
return new And.SecKeySelectionStrategy(
|
options = ProducerOptions.noEncryptionNoSigning();
|
||||||
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.");
|
|
||||||
}
|
}
|
||||||
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,11 +25,10 @@ import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||||
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
||||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||||
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
|
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
|
||||||
import org.pgpainless.algorithm.CompressionAlgorithm;
|
import org.pgpainless.algorithm.DocumentSignatureType;
|
||||||
import org.pgpainless.algorithm.HashAlgorithm;
|
|
||||||
import org.pgpainless.algorithm.StreamEncoding;
|
import org.pgpainless.algorithm.StreamEncoding;
|
||||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
|
||||||
import org.pgpainless.decryption_verification.OpenPgpMetadata;
|
import org.pgpainless.decryption_verification.OpenPgpMetadata;
|
||||||
|
import org.pgpainless.exception.KeyValidationException;
|
||||||
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
||||||
import org.pgpainless.util.Passphrase;
|
import org.pgpainless.util.Passphrase;
|
||||||
|
|
||||||
|
@ -42,7 +41,7 @@ public interface EncryptionBuilderInterface {
|
||||||
* @param outputStream output stream of the plain data.
|
* @param outputStream output stream of the plain data.
|
||||||
* @return api handle
|
* @return api handle
|
||||||
*/
|
*/
|
||||||
default ToRecipients onOutputStream(@Nonnull OutputStream outputStream) {
|
default ToRecipientsOrNoEncryption onOutputStream(@Nonnull OutputStream outputStream) {
|
||||||
return onOutputStream(outputStream, OpenPgpMetadata.FileInfo.binaryStream());
|
return onOutputStream(outputStream, OpenPgpMetadata.FileInfo.binaryStream());
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
|
@ -55,7 +54,7 @@ public interface EncryptionBuilderInterface {
|
||||||
*
|
*
|
||||||
* @deprecated use {@link #onOutputStream(OutputStream, OpenPgpMetadata.FileInfo)} instead.
|
* @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());
|
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.
|
* @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));
|
return onOutputStream(outputStream, new OpenPgpMetadata.FileInfo(forYourEyesOnly ? "_CONSOLE" : fileName, new Date(), StreamEncoding.BINARY));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,102 +81,99 @@ public interface EncryptionBuilderInterface {
|
||||||
* @param fileInfo file information
|
* @param fileInfo file information
|
||||||
* @return api handle
|
* @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.
|
* @param options options
|
||||||
* @return api handle
|
* @return encryption strea
|
||||||
*/
|
*/
|
||||||
WithAlgorithms toRecipients(@Nonnull PGPPublicKeyRing... keys);
|
EncryptionStream withOptions(ProducerOptions options) throws PGPException, IOException;
|
||||||
|
|
||||||
/**
|
|
||||||
* 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);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instruct the {@link EncryptionStream} to not encrypt any data.
|
* Instruct the {@link EncryptionStream} to not encrypt any data.
|
||||||
*
|
*
|
||||||
* @return api handle
|
* @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.
|
* Add an additional recipient key/passphrase or configure signing.
|
||||||
*
|
|
||||||
* @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.
|
|
||||||
*
|
*
|
||||||
* @return api handle
|
* @return api handle
|
||||||
*/
|
*/
|
||||||
DetachedSign usingSecureAlgorithms();
|
ToRecipientsOrSign and();
|
||||||
|
|
||||||
ToRecipients and();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DetachedSign extends SignWith {
|
// Allow additional recipient or signing configuration
|
||||||
|
interface ToRecipientsOrSign extends ToRecipients, SignWithOrDontSign {
|
||||||
/**
|
}
|
||||||
* 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 signing configuration or no signing at all
|
||||||
|
interface SignWithOrDontSign extends SignWith {
|
||||||
/**
|
/**
|
||||||
* Do not sign the plain data at all.
|
* Do not sign the plain data at all.
|
||||||
*
|
*
|
||||||
* @return api handle
|
* @return api handle
|
||||||
*/
|
*/
|
||||||
Armor doNotSign();
|
Armor doNotSign();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SignWith {
|
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
|
* Pass in a list of secret keys used for signing, along with a {@link SecretKeyRingProtector} used to unlock
|
||||||
* the secret keys.
|
* the secret keys.
|
||||||
*
|
*
|
||||||
|
* @deprecated use {@link #signInlineWith(SecretKeyRingProtector, PGPSecretKeyRing)} instead.
|
||||||
* @param decryptor {@link SecretKeyRingProtector} used to unlock the secret keys
|
* @param decryptor {@link SecretKeyRingProtector} used to unlock the secret keys
|
||||||
* @param keyRings secret keys used for signing
|
* @param keyRings secret keys used for signing
|
||||||
* @return api handle
|
* @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 {
|
interface AdditionalSignWith extends Armor {
|
||||||
|
/**
|
||||||
Armor signBinaryDocument();
|
* Add an additional signing key/method.
|
||||||
|
*
|
||||||
Armor signCanonicalText();
|
* @return api handle
|
||||||
|
*/
|
||||||
|
SignWith and();
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Armor {
|
interface Armor {
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,10 +17,6 @@ package org.pgpainless.encryption_signing;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
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.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
|
@ -31,29 +27,18 @@ import org.bouncycastle.openpgp.PGPCompressedDataGenerator;
|
||||||
import org.bouncycastle.openpgp.PGPEncryptedDataGenerator;
|
import org.bouncycastle.openpgp.PGPEncryptedDataGenerator;
|
||||||
import org.bouncycastle.openpgp.PGPException;
|
import org.bouncycastle.openpgp.PGPException;
|
||||||
import org.bouncycastle.openpgp.PGPLiteralDataGenerator;
|
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.PGPSignature;
|
||||||
import org.bouncycastle.openpgp.PGPSignatureGenerator;
|
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.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.bc.BcPGPDataEncryptorBuilder;
|
||||||
import org.bouncycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder;
|
import org.bouncycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder;
|
||||||
import org.pgpainless.algorithm.CompressionAlgorithm;
|
import org.pgpainless.algorithm.CompressionAlgorithm;
|
||||||
import org.pgpainless.algorithm.HashAlgorithm;
|
|
||||||
import org.pgpainless.algorithm.SignatureType;
|
|
||||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
||||||
import org.pgpainless.decryption_verification.OpenPgpMetadata;
|
import org.pgpainless.decryption_verification.OpenPgpMetadata;
|
||||||
import org.pgpainless.implementation.ImplementationFactory;
|
import org.pgpainless.implementation.ImplementationFactory;
|
||||||
import org.pgpainless.key.SubkeyIdentifier;
|
import org.pgpainless.key.SubkeyIdentifier;
|
||||||
import org.pgpainless.signature.DetachedSignature;
|
|
||||||
import org.pgpainless.util.ArmoredOutputStreamFactory;
|
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.
|
* 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 Logger LOGGER = Logger.getLogger(EncryptionStream.class.getName());
|
||||||
private static final Level LEVEL = Level.FINE;
|
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 static final int BUFFER_SIZE = 1 << 8;
|
||||||
|
|
||||||
private final SymmetricKeyAlgorithm symmetricKeyAlgorithm;
|
OutputStream outermostStream;
|
||||||
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;
|
|
||||||
|
|
||||||
private ArmoredOutputStream armorOutputStream = null;
|
private ArmoredOutputStream armorOutputStream = null;
|
||||||
private OutputStream publicKeyEncryptedStream = null;
|
private OutputStream publicKeyEncryptedStream = null;
|
||||||
|
|
||||||
private PGPCompressedDataGenerator compressedDataGenerator;
|
private PGPCompressedDataGenerator compressedDataGenerator;
|
||||||
private BCPGOutputStream basicCompressionStream;
|
private BCPGOutputStream basicCompressionStream;
|
||||||
|
|
||||||
private PGPLiteralDataGenerator literalDataGenerator;
|
private PGPLiteralDataGenerator literalDataGenerator;
|
||||||
private OutputStream literalDataStream;
|
private OutputStream literalDataStream;
|
||||||
|
|
||||||
EncryptionStream(@Nonnull OutputStream targetOutputStream,
|
EncryptionStream(@Nonnull OutputStream targetOutputStream,
|
||||||
@Nonnull Map<SubkeyIdentifier, PGPPublicKeyRing> encryptionKeys,
|
@Nonnull ProducerOptions options,
|
||||||
@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 OpenPgpMetadata.FileInfo fileInfo)
|
@Nonnull OpenPgpMetadata.FileInfo fileInfo)
|
||||||
throws IOException, PGPException {
|
throws IOException, PGPException {
|
||||||
|
this.options = options;
|
||||||
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;
|
|
||||||
|
|
||||||
outermostStream = targetOutputStream;
|
outermostStream = targetOutputStream;
|
||||||
|
|
||||||
prepareArmor();
|
prepareArmor();
|
||||||
prepareEncryption();
|
prepareEncryption();
|
||||||
prepareSigning();
|
|
||||||
prepareCompression();
|
prepareCompression();
|
||||||
prepareOnePassSignatures();
|
prepareOnePassSignatures();
|
||||||
prepareLiteralDataProcessing(fileInfo);
|
prepareLiteralDataProcessing(fileInfo);
|
||||||
prepareResultBuilder();
|
|
||||||
resultBuilder.setFileInfo(fileInfo);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void prepareArmor() {
|
private void prepareArmor() {
|
||||||
if (!asciiArmor) {
|
if (!options.isAsciiArmor()) {
|
||||||
LOGGER.log(LEVEL, "Encryption output will be binary");
|
LOGGER.log(LEVEL, "Encryption output will be binary");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -155,14 +106,18 @@ public final class EncryptionStream extends OutputStream {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void prepareEncryption() throws IOException, PGPException {
|
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;
|
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 =
|
PGPDataEncryptorBuilder dataEncryptorBuilder =
|
||||||
ImplementationFactory.getInstance().getPGPDataEncryptorBuilder(symmetricKeyAlgorithm);
|
ImplementationFactory.getInstance().getPGPDataEncryptorBuilder(encryptionAlgorithm);
|
||||||
|
|
||||||
// Simplify once https://github.com/bcgit/bc-java/pull/859 is merged
|
// Simplify once https://github.com/bcgit/bc-java/pull/859 is merged
|
||||||
if (dataEncryptorBuilder instanceof BcPGPDataEncryptorBuilder) {
|
if (dataEncryptorBuilder instanceof BcPGPDataEncryptorBuilder) {
|
||||||
((BcPGPDataEncryptorBuilder) dataEncryptorBuilder).setWithIntegrityPacket(true);
|
((BcPGPDataEncryptorBuilder) dataEncryptorBuilder).setWithIntegrityPacket(true);
|
||||||
|
@ -172,46 +127,21 @@ public final class EncryptionStream extends OutputStream {
|
||||||
|
|
||||||
PGPEncryptedDataGenerator encryptedDataGenerator =
|
PGPEncryptedDataGenerator encryptedDataGenerator =
|
||||||
new PGPEncryptedDataGenerator(dataEncryptorBuilder);
|
new PGPEncryptedDataGenerator(dataEncryptorBuilder);
|
||||||
|
for (PGPKeyEncryptionMethodGenerator encryptionMethod : encryptionOptions.getEncryptionMethods()) {
|
||||||
for (SubkeyIdentifier keyIdentifier : encryptionKeys.keySet()) {
|
encryptedDataGenerator.addMethod(encryptionMethod);
|
||||||
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 (Passphrase passphrase : encryptionPassphrases) {
|
for (SubkeyIdentifier recipientSubkeyIdentifier : encryptionOptions.getEncryptionKeyIdentifiers()) {
|
||||||
PBEKeyEncryptionMethodGenerator passphraseEncryption =
|
resultBuilder.addRecipient(recipientSubkeyIdentifier);
|
||||||
ImplementationFactory.getInstance().getPBEKeyEncryptionMethodGenerator(passphrase);
|
|
||||||
encryptedDataGenerator.addMethod(passphraseEncryption);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
publicKeyEncryptedStream = encryptedDataGenerator.open(outermostStream, new byte[BUFFER_SIZE]);
|
publicKeyEncryptedStream = encryptedDataGenerator.open(outermostStream, new byte[BUFFER_SIZE]);
|
||||||
outermostStream = publicKeyEncryptedStream;
|
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 {
|
private void prepareCompression() throws IOException {
|
||||||
|
CompressionAlgorithm compressionAlgorithm = options.getCompressionAlgorithmOverride();
|
||||||
|
resultBuilder.setCompressionAlgorithm(compressionAlgorithm);
|
||||||
compressedDataGenerator = new PGPCompressedDataGenerator(
|
compressedDataGenerator = new PGPCompressedDataGenerator(
|
||||||
compressionAlgorithm.getAlgorithmId());
|
compressionAlgorithm.getAlgorithmId());
|
||||||
if (compressionAlgorithm == CompressionAlgorithm.UNCOMPRESSED) {
|
if (compressionAlgorithm == CompressionAlgorithm.UNCOMPRESSED) {
|
||||||
|
@ -224,9 +154,19 @@ public final class EncryptionStream extends OutputStream {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void prepareOnePassSignatures() throws IOException, PGPException {
|
private void prepareOnePassSignatures() throws IOException, PGPException {
|
||||||
for (SubkeyIdentifier identifier : signatureGenerators.keySet()) {
|
SigningOptions signingOptions = options.getSigningOptions();
|
||||||
PGPSignatureGenerator signatureGenerator = signatureGenerators.get(identifier).getSecond();
|
if (signingOptions == null || signingOptions.getSigningMethods().isEmpty()) {
|
||||||
signatureGenerator.generateOnePassVersion(false).encode(outermostStream);
|
// 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(),
|
fileInfo.getModificationDate(),
|
||||||
new byte[BUFFER_SIZE]);
|
new byte[BUFFER_SIZE]);
|
||||||
outermostStream = literalDataStream;
|
outermostStream = literalDataStream;
|
||||||
}
|
resultBuilder.setFileInfo(fileInfo);
|
||||||
|
|
||||||
private void prepareResultBuilder() {
|
|
||||||
for (SubkeyIdentifier recipient : encryptionKeys.keySet()) {
|
|
||||||
resultBuilder.addRecipientKeyId(recipient.getSubkeyFingerprint().getKeyId());
|
|
||||||
}
|
|
||||||
resultBuilder.setSymmetricKeyAlgorithm(symmetricKeyAlgorithm);
|
|
||||||
resultBuilder.setCompressionAlgorithm(compressionAlgorithm);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void write(int data) throws IOException {
|
public void write(int data) throws IOException {
|
||||||
outermostStream.write(data);
|
outermostStream.write(data);
|
||||||
|
SigningOptions signingOptions = options.getSigningOptions();
|
||||||
|
if (signingOptions == null || signingOptions.getSigningMethods().isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (SubkeyIdentifier signingKey : signatureGenerators.keySet()) {
|
for (SubkeyIdentifier signingKey : signingOptions.getSigningMethods().keySet()) {
|
||||||
PGPSignatureGenerator signatureGenerator = signatureGenerators.get(signingKey).getSecond();
|
SigningOptions.SigningMethod signingMethod = signingOptions.getSigningMethods().get(signingKey);
|
||||||
|
PGPSignatureGenerator signatureGenerator = signingMethod.getSignatureGenerator();
|
||||||
byte asByte = (byte) (data & 0xff);
|
byte asByte = (byte) (data & 0xff);
|
||||||
signatureGenerator.update(asByte);
|
signatureGenerator.update(asByte);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void write(byte[] buffer) throws IOException {
|
public void write(@Nonnull byte[] buffer) throws IOException {
|
||||||
write(buffer, 0, buffer.length);
|
write(buffer, 0, buffer.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@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);
|
outermostStream.write(buffer, 0, len);
|
||||||
for (SubkeyIdentifier signingKey : signatureGenerators.keySet()) {
|
SigningOptions signingOptions = options.getSigningOptions();
|
||||||
PGPSignatureGenerator signatureGenerator = signatureGenerators.get(signingKey).getSecond();
|
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);
|
signatureGenerator.update(buffer, 0, len);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -314,19 +257,23 @@ public final class EncryptionStream extends OutputStream {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeSignatures() throws PGPException, IOException {
|
private void writeSignatures() throws PGPException, IOException {
|
||||||
for (SubkeyIdentifier signingKey : signatureGenerators.keySet()) {
|
SigningOptions signingOptions = options.getSigningOptions();
|
||||||
PGPSignatureGenerator signatureGenerator = signatureGenerators.get(signingKey).getSecond();
|
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();
|
PGPSignature signature = signatureGenerator.generate();
|
||||||
if (!detachedSignature) {
|
if (signingMethod.isDetached()) {
|
||||||
|
resultBuilder.addDetachedSignature(signingKey, signature);
|
||||||
|
} else {
|
||||||
signature.encode(outermostStream);
|
signature.encode(outermostStream);
|
||||||
}
|
}
|
||||||
DetachedSignature detachedSignature = new DetachedSignature(
|
|
||||||
signature, signatureGenerators.get(signingKey).getFirst(), signingKey);
|
|
||||||
resultBuilder.addDetachedSignature(detachedSignature);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public OpenPgpMetadata getResult() {
|
public EncryptionResult getResult() {
|
||||||
if (!closed) {
|
if (!closed) {
|
||||||
throw new IllegalStateException("EncryptionStream must be closed before accessing the Result.");
|
throw new IllegalStateException("EncryptionStream must be closed before accessing the Result.");
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -62,6 +62,14 @@ public class SubkeyIdentifier {
|
||||||
this.subkeyFingerprint = subkeyFingerprint;
|
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.
|
* 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.
|
* 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 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.
|
* Return the {@link OpenPgpV4Fingerprint} of the identified subkey.
|
||||||
*
|
*
|
||||||
|
@ -81,6 +99,15 @@ public class SubkeyIdentifier {
|
||||||
return subkeyFingerprint;
|
return subkeyFingerprint;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the key id of the identified subkey.
|
||||||
|
*
|
||||||
|
* @return subkey id
|
||||||
|
*/
|
||||||
|
public long getSubkeyId() {
|
||||||
|
return getSubkeyFingerprint().getKeyId();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return primaryKeyFingerprint.hashCode() * 31 + subkeyFingerprint.hashCode();
|
return primaryKeyFingerprint.hashCode() * 31 + subkeyFingerprint.hashCode();
|
||||||
|
|
|
@ -20,12 +20,12 @@ import static org.pgpainless.util.CollectionUtils.iteratorToList;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
@ -37,9 +37,14 @@ import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||||
import org.bouncycastle.openpgp.PGPSecretKey;
|
import org.bouncycastle.openpgp.PGPSecretKey;
|
||||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||||
import org.bouncycastle.openpgp.PGPSignature;
|
import org.bouncycastle.openpgp.PGPSignature;
|
||||||
|
import org.pgpainless.PGPainless;
|
||||||
|
import org.pgpainless.algorithm.HashAlgorithm;
|
||||||
import org.pgpainless.algorithm.KeyFlag;
|
import org.pgpainless.algorithm.KeyFlag;
|
||||||
import org.pgpainless.algorithm.PublicKeyAlgorithm;
|
import org.pgpainless.algorithm.PublicKeyAlgorithm;
|
||||||
|
import org.pgpainless.encryption_signing.EncryptionStream;
|
||||||
|
import org.pgpainless.exception.KeyValidationException;
|
||||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||||
|
import org.pgpainless.policy.Policy;
|
||||||
import org.pgpainless.signature.SignaturePicker;
|
import org.pgpainless.signature.SignaturePicker;
|
||||||
import org.pgpainless.signature.SignatureUtils;
|
import org.pgpainless.signature.SignatureUtils;
|
||||||
import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil;
|
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 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 PGPKeyRing keys;
|
||||||
|
private Signatures signatures;
|
||||||
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<>();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Evaluate the key ring at creation time of the given signature.
|
* Evaluate the key ring at creation time of the given signature.
|
||||||
|
@ -82,36 +81,7 @@ public class KeyRingInfo {
|
||||||
|
|
||||||
public KeyRingInfo(PGPKeyRing keys, Date validationDate) {
|
public KeyRingInfo(PGPKeyRing keys, Date validationDate) {
|
||||||
this.keys = keys;
|
this.keys = keys;
|
||||||
|
this.signatures = new Signatures(keys, validationDate, PGPainless.getPolicy());
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -142,11 +112,11 @@ public class KeyRingInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (publicKey == getPublicKey()) {
|
if (publicKey == getPublicKey()) {
|
||||||
return revocationSelfSignature == null;
|
return signatures.primaryKeyRevocation == null;
|
||||||
}
|
}
|
||||||
|
|
||||||
PGPSignature binding = mostRecentSubkeyBindings.get(keyId);
|
PGPSignature binding = signatures.subkeyBindings.get(keyId);
|
||||||
PGPSignature revocation = mostRecentSubkeyRevocations.get(keyId);
|
PGPSignature revocation = signatures.subkeyRevocations.get(keyId);
|
||||||
return binding != null && revocation == null;
|
return binding != null && revocation == null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -225,7 +195,7 @@ public class KeyRingInfo {
|
||||||
String primaryUserId = null;
|
String primaryUserId = null;
|
||||||
Date modificationDate = null;
|
Date modificationDate = null;
|
||||||
for (String userId : getValidUserIds()) {
|
for (String userId : getValidUserIds()) {
|
||||||
PGPSignature signature = mostRecentUserIdSignatures.get(userId);
|
PGPSignature signature = signatures.userIdCertifications.get(userId);
|
||||||
PrimaryUserID subpacket = SignatureSubpacketsUtil.getPrimaryUserId(signature);
|
PrimaryUserID subpacket = SignatureSubpacketsUtil.getPrimaryUserId(signature);
|
||||||
if (subpacket != null && subpacket.isPrimaryUserID()) {
|
if (subpacket != null && subpacket.isPrimaryUserID()) {
|
||||||
// if there are multiple primary userIDs, return most recently signed
|
// 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;
|
return primaryUserId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -261,8 +236,8 @@ public class KeyRingInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isUserIdValid(String userId) {
|
public boolean isUserIdValid(String userId) {
|
||||||
PGPSignature certification = mostRecentUserIdSignatures.get(userId);
|
PGPSignature certification = signatures.userIdCertifications.get(userId);
|
||||||
PGPSignature revocation = mostRecentUserIdRevocations.get(userId);
|
PGPSignature revocation = signatures.userIdRevocations.get(userId);
|
||||||
|
|
||||||
return certification != null && revocation == null;
|
return certification != null && revocation == null;
|
||||||
}
|
}
|
||||||
|
@ -285,34 +260,35 @@ public class KeyRingInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
public PGPSignature getCurrentDirectKeySelfSignature() {
|
public PGPSignature getCurrentDirectKeySelfSignature() {
|
||||||
return mostRecentSelfSignature;
|
return signatures.primaryKeySelfSignature;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PGPSignature getRevocationSelfSignature() {
|
public PGPSignature getRevocationSelfSignature() {
|
||||||
return revocationSelfSignature;
|
return signatures.primaryKeyRevocation;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PGPSignature getCurrentUserIdCertification(String userId) {
|
public PGPSignature getCurrentUserIdCertification(String userId) {
|
||||||
return mostRecentUserIdSignatures.get(userId);
|
return signatures.userIdCertifications.get(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PGPSignature getUserIdRevocation(String userId) {
|
public PGPSignature getUserIdRevocation(String userId) {
|
||||||
return mostRecentUserIdRevocations.get(userId);
|
return signatures.userIdRevocations.get(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PGPSignature getCurrentSubkeyBindingSignature(long keyId) {
|
public PGPSignature getCurrentSubkeyBindingSignature(long keyId) {
|
||||||
return mostRecentSubkeyBindings.get(keyId);
|
return signatures.subkeyBindings.get(keyId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PGPSignature getSubkeyRevocationSignature(long keyId) {
|
public PGPSignature getSubkeyRevocationSignature(long keyId) {
|
||||||
return mostRecentSubkeyRevocations.get(keyId);
|
return signatures.subkeyRevocations.get(keyId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<KeyFlag> getKeyFlagsOf(long keyId) {
|
public List<KeyFlag> getKeyFlagsOf(long keyId) {
|
||||||
if (getPublicKey().getKeyID() == keyId) {
|
if (getPublicKey().getKeyID() == keyId) {
|
||||||
|
|
||||||
if (mostRecentSelfSignature != null) {
|
PGPSignature directKeySignature = getCurrentDirectKeySelfSignature();
|
||||||
KeyFlags flags = SignatureSubpacketsUtil.getKeyFlags(mostRecentSelfSignature);
|
if (directKeySignature != null) {
|
||||||
|
KeyFlags flags = SignatureSubpacketsUtil.getKeyFlags(directKeySignature);
|
||||||
if (flags != null) {
|
if (flags != null) {
|
||||||
return KeyFlag.fromBitmask(flags.getFlags());
|
return KeyFlag.fromBitmask(flags.getFlags());
|
||||||
}
|
}
|
||||||
|
@ -320,7 +296,15 @@ public class KeyRingInfo {
|
||||||
|
|
||||||
String primaryUserId = getPrimaryUserId();
|
String primaryUserId = getPrimaryUserId();
|
||||||
if (primaryUserId != null) {
|
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) {
|
if (flags != null) {
|
||||||
return KeyFlag.fromBitmask(flags.getFlags());
|
return KeyFlag.fromBitmask(flags.getFlags());
|
||||||
}
|
}
|
||||||
|
@ -334,7 +318,7 @@ public class KeyRingInfo {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
PGPSignature userIdCertification = mostRecentUserIdSignatures.get(userId);
|
PGPSignature userIdCertification = getCurrentUserIdCertification(userId);
|
||||||
if (userIdCertification == null) {
|
if (userIdCertification == null) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
@ -377,12 +361,14 @@ public class KeyRingInfo {
|
||||||
|
|
||||||
private PGPSignature getMostRecentSignature() {
|
private PGPSignature getMostRecentSignature() {
|
||||||
Set<PGPSignature> allSignatures = new HashSet<>();
|
Set<PGPSignature> allSignatures = new HashSet<>();
|
||||||
|
PGPSignature mostRecentSelfSignature = getCurrentDirectKeySelfSignature();
|
||||||
|
PGPSignature revocationSelfSignature = getRevocationSelfSignature();
|
||||||
if (mostRecentSelfSignature != null) allSignatures.add(mostRecentSelfSignature);
|
if (mostRecentSelfSignature != null) allSignatures.add(mostRecentSelfSignature);
|
||||||
if (revocationSelfSignature != null) allSignatures.add(revocationSelfSignature);
|
if (revocationSelfSignature != null) allSignatures.add(revocationSelfSignature);
|
||||||
allSignatures.addAll(mostRecentUserIdSignatures.values());
|
allSignatures.addAll(signatures.userIdCertifications.values());
|
||||||
allSignatures.addAll(mostRecentUserIdRevocations.values());
|
allSignatures.addAll(signatures.userIdRevocations.values());
|
||||||
allSignatures.addAll(mostRecentSubkeyBindings.values());
|
allSignatures.addAll(signatures.subkeyBindings.values());
|
||||||
allSignatures.addAll(mostRecentSubkeyRevocations.values());
|
allSignatures.addAll(signatures.subkeyRevocations.values());
|
||||||
|
|
||||||
PGPSignature mostRecent = null;
|
PGPSignature mostRecent = null;
|
||||||
for (PGPSignature signature : allSignatures) {
|
for (PGPSignature signature : allSignatures) {
|
||||||
|
@ -399,7 +385,7 @@ public class KeyRingInfo {
|
||||||
* @return revocation date or null
|
* @return revocation date or null
|
||||||
*/
|
*/
|
||||||
public Date getRevocationDate() {
|
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() {
|
public Date getPrimaryKeyExpirationDate() {
|
||||||
Date lastExpiration = null;
|
Date lastExpiration = null;
|
||||||
if (mostRecentSelfSignature != null) {
|
if (getCurrentDirectKeySelfSignature() != null) {
|
||||||
lastExpiration = SignatureUtils.getKeyExpirationDate(getCreationDate(), mostRecentSelfSignature);
|
lastExpiration = SignatureUtils.getKeyExpirationDate(getCreationDate(), getCurrentDirectKeySelfSignature());
|
||||||
}
|
}
|
||||||
|
|
||||||
for (String userId : getValidUserIds()) {
|
for (String userId : getValidUserIds()) {
|
||||||
|
@ -432,7 +418,7 @@ public class KeyRingInfo {
|
||||||
if (subkey == null) {
|
if (subkey == null) {
|
||||||
throw new IllegalArgumentException("No subkey with fingerprint " + fingerprint + " found.");
|
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;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.pgpainless.algorithm.CompressionAlgorithm;
|
||||||
import org.pgpainless.algorithm.HashAlgorithm;
|
import org.pgpainless.algorithm.HashAlgorithm;
|
||||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
||||||
import org.pgpainless.util.NotationRegistry;
|
import org.pgpainless.util.NotationRegistry;
|
||||||
|
@ -38,6 +39,8 @@ public final class Policy {
|
||||||
SymmetricKeyAlgorithmPolicy.defaultSymmetricKeyEncryptionAlgorithmPolicy();
|
SymmetricKeyAlgorithmPolicy.defaultSymmetricKeyEncryptionAlgorithmPolicy();
|
||||||
private SymmetricKeyAlgorithmPolicy symmetricKeyDecryptionAlgorithmPolicy =
|
private SymmetricKeyAlgorithmPolicy symmetricKeyDecryptionAlgorithmPolicy =
|
||||||
SymmetricKeyAlgorithmPolicy.defaultSymmetricKeyDecryptionAlgorithmPolicy();
|
SymmetricKeyAlgorithmPolicy.defaultSymmetricKeyDecryptionAlgorithmPolicy();
|
||||||
|
private CompressionAlgorithmPolicy compressionAlgorithmPolicy =
|
||||||
|
CompressionAlgorithmPolicy.defaultCompressionAlgorithmPolicy();
|
||||||
private final NotationRegistry notationRegistry = new NotationRegistry();
|
private final NotationRegistry notationRegistry = new NotationRegistry();
|
||||||
|
|
||||||
private Policy() {
|
private Policy() {
|
||||||
|
@ -142,6 +145,17 @@ public final class Policy {
|
||||||
this.symmetricKeyDecryptionAlgorithmPolicy = 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 {
|
public static final class SymmetricKeyAlgorithmPolicy {
|
||||||
|
|
||||||
private final SymmetricKeyAlgorithm defaultSymmetricKeyAlgorithm;
|
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.
|
* Return the {@link NotationRegistry} of PGPainless.
|
||||||
* The notation registry is used to decide, whether or not a Notation is known or not.
|
* The notation registry is used to decide, whether or not a Notation is known or not.
|
||||||
|
|
|
@ -59,7 +59,7 @@ public abstract class SelectSignatureFromKey {
|
||||||
* Criterion that checks if the signature is valid at the validation date.
|
* 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.
|
* 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 ≤ validationDate < expirationDate.
|
||||||
*
|
*
|
||||||
* @param validationDate validation date
|
* @param validationDate validation date
|
||||||
* @return criterion implementation
|
* @return criterion implementation
|
||||||
|
|
|
@ -25,8 +25,11 @@ import org.bouncycastle.bcpg.sig.SignatureCreationTime;
|
||||||
import org.bouncycastle.openpgp.PGPKeyRing;
|
import org.bouncycastle.openpgp.PGPKeyRing;
|
||||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||||
import org.bouncycastle.openpgp.PGPSignature;
|
import org.bouncycastle.openpgp.PGPSignature;
|
||||||
|
import org.pgpainless.PGPainless;
|
||||||
import org.pgpainless.algorithm.SignatureType;
|
import org.pgpainless.algorithm.SignatureType;
|
||||||
|
import org.pgpainless.exception.SignatureValidationException;
|
||||||
import org.pgpainless.key.util.RevocationAttributes;
|
import org.pgpainless.key.util.RevocationAttributes;
|
||||||
|
import org.pgpainless.policy.Policy;
|
||||||
import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil;
|
import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil;
|
||||||
import org.pgpainless.util.CollectionUtils;
|
import org.pgpainless.util.CollectionUtils;
|
||||||
|
|
||||||
|
@ -53,36 +56,19 @@ public class SignaturePicker {
|
||||||
* @return most recent, valid key revocation signature
|
* @return most recent, valid key revocation signature
|
||||||
*/
|
*/
|
||||||
public static PGPSignature pickCurrentRevocationSelfSignature(PGPKeyRing keyRing, Date validationDate) {
|
public static PGPSignature pickCurrentRevocationSelfSignature(PGPKeyRing keyRing, Date validationDate) {
|
||||||
|
Policy policy = PGPainless.getPolicy();
|
||||||
PGPPublicKey primaryKey = keyRing.getPublicKey();
|
PGPPublicKey primaryKey = keyRing.getPublicKey();
|
||||||
|
|
||||||
List<PGPSignature> signatures = getSortedSignaturesOfType(primaryKey, SignatureType.KEY_REVOCATION);
|
List<PGPSignature> signatures = getSortedSignaturesOfType(primaryKey, SignatureType.KEY_REVOCATION);
|
||||||
PGPSignature mostCurrentValidSig = null;
|
PGPSignature mostCurrentValidSig = null;
|
||||||
|
|
||||||
for (PGPSignature signature : signatures) {
|
for (PGPSignature signature : signatures) {
|
||||||
if (!SelectSignatureFromKey.isWellFormed().accept(signature, primaryKey, keyRing)) {
|
try {
|
||||||
// Signature is not well-formed. Reject
|
SignatureValidator.verifyKeyRevocationSignature(signature, primaryKey, policy, validationDate);
|
||||||
|
} catch (SignatureValidationException e) {
|
||||||
|
// Signature is not valid
|
||||||
continue;
|
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;
|
mostCurrentValidSig = signature;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,8 @@ import java.util.Arrays;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
import org.bouncycastle.bcpg.sig.Exportable;
|
import org.bouncycastle.bcpg.sig.Exportable;
|
||||||
import org.bouncycastle.bcpg.sig.Features;
|
import org.bouncycastle.bcpg.sig.Features;
|
||||||
import org.bouncycastle.bcpg.sig.IntendedRecipientFingerprint;
|
import org.bouncycastle.bcpg.sig.IntendedRecipientFingerprint;
|
||||||
|
@ -44,7 +46,11 @@ import org.bouncycastle.openpgp.PGPSignature;
|
||||||
import org.bouncycastle.openpgp.PGPSignatureList;
|
import org.bouncycastle.openpgp.PGPSignatureList;
|
||||||
import org.bouncycastle.openpgp.PGPSignatureSubpacketVector;
|
import org.bouncycastle.openpgp.PGPSignatureSubpacketVector;
|
||||||
import org.bouncycastle.util.encoders.Hex;
|
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.SignatureSubpacket;
|
||||||
|
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
||||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||||
import org.pgpainless.signature.SignatureUtils;
|
import org.pgpainless.signature.SignatureUtils;
|
||||||
|
|
||||||
|
@ -200,6 +206,17 @@ public class SignatureSubpacketsUtil {
|
||||||
return hashed(signature, SignatureSubpacket.preferredSymmetricAlgorithms);
|
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.
|
* Return the hash algorithm preferences from the signatures hashed area.
|
||||||
*
|
*
|
||||||
|
@ -210,6 +227,17 @@ public class SignatureSubpacketsUtil {
|
||||||
return hashed(signature, SignatureSubpacket.preferredHashAlgorithms);
|
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.
|
* Return the compression algorithm preferences from the signatures hashed area.
|
||||||
*
|
*
|
||||||
|
@ -220,6 +248,17 @@ public class SignatureSubpacketsUtil {
|
||||||
return hashed(signature, SignatureSubpacket.preferredCompressionAlgorithms);
|
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.
|
* Return the primary user-id subpacket from the signatures hashed area.
|
||||||
*
|
*
|
||||||
|
@ -240,6 +279,24 @@ public class SignatureSubpacketsUtil {
|
||||||
return hashed(signature, SignatureSubpacket.keyFlags);
|
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.
|
* Return the features subpacket from the signatures hashed area.
|
||||||
*
|
*
|
||||||
|
|
|
@ -17,18 +17,23 @@ package org.bouncycastle;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
import java.security.InvalidAlgorithmParameterException;
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import org.bouncycastle.openpgp.PGPException;
|
import org.bouncycastle.openpgp.PGPException;
|
||||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||||
|
import org.bouncycastle.openpgp.PGPSignature;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.pgpainless.PGPainless;
|
import org.pgpainless.PGPainless;
|
||||||
import org.pgpainless.key.util.KeyRingUtils;
|
import org.pgpainless.key.util.KeyRingUtils;
|
||||||
|
import org.pgpainless.util.CollectionUtils;
|
||||||
|
|
||||||
public class PGPPublicKeyRingTest {
|
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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,12 +41,13 @@ import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.params.ParameterizedTest;
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
import org.junit.jupiter.params.provider.MethodSource;
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
import org.pgpainless.PGPainless;
|
import org.pgpainless.PGPainless;
|
||||||
|
import org.pgpainless.algorithm.DocumentSignatureType;
|
||||||
import org.pgpainless.algorithm.KeyFlag;
|
import org.pgpainless.algorithm.KeyFlag;
|
||||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
||||||
import org.pgpainless.decryption_verification.DecryptionStream;
|
import org.pgpainless.decryption_verification.DecryptionStream;
|
||||||
import org.pgpainless.decryption_verification.OpenPgpMetadata;
|
import org.pgpainless.decryption_verification.OpenPgpMetadata;
|
||||||
import org.pgpainless.implementation.ImplementationFactory;
|
import org.pgpainless.implementation.ImplementationFactory;
|
||||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
import org.pgpainless.key.SubkeyIdentifier;
|
||||||
import org.pgpainless.key.TestKeys;
|
import org.pgpainless.key.TestKeys;
|
||||||
import org.pgpainless.key.generation.KeySpec;
|
import org.pgpainless.key.generation.KeySpec;
|
||||||
import org.pgpainless.key.generation.type.KeyType;
|
import org.pgpainless.key.generation.type.KeyType;
|
||||||
|
@ -155,26 +156,20 @@ public class EncryptDecryptTest {
|
||||||
|
|
||||||
EncryptionStream encryptor = PGPainless.encryptAndOrSign()
|
EncryptionStream encryptor = PGPainless.encryptAndOrSign()
|
||||||
.onOutputStream(envelope)
|
.onOutputStream(envelope)
|
||||||
.toRecipients(recipientPub)
|
.toRecipient(recipientPub)
|
||||||
.usingSecureAlgorithms()
|
.and()
|
||||||
.signWith(keyDecryptor, senderSec)
|
.signInlineWith(keyDecryptor, senderSec, null, DocumentSignatureType.BINARY_DOCUMENT)
|
||||||
.signBinaryDocument()
|
|
||||||
.noArmor();
|
.noArmor();
|
||||||
|
|
||||||
Streams.pipeAll(new ByteArrayInputStream(secretMessage), encryptor);
|
Streams.pipeAll(new ByteArrayInputStream(secretMessage), encryptor);
|
||||||
encryptor.close();
|
encryptor.close();
|
||||||
byte[] encryptedSecretMessage = envelope.toByteArray();
|
byte[] encryptedSecretMessage = envelope.toByteArray();
|
||||||
|
|
||||||
OpenPgpMetadata encryptionResult = encryptor.getResult();
|
EncryptionResult encryptionResult = encryptor.getResult();
|
||||||
|
|
||||||
assertFalse(encryptionResult.getSignatures().isEmpty());
|
assertFalse(encryptionResult.getRecipients().isEmpty());
|
||||||
for (OpenPgpV4Fingerprint fingerprint : encryptionResult.getVerifiedSignatures().keySet()) {
|
for (SubkeyIdentifier encryptionKey : encryptionResult.getRecipients()) {
|
||||||
assertTrue(BCUtil.keyRingContainsKeyWithId(senderPub, fingerprint.getKeyId()));
|
assertTrue(BCUtil.keyRingContainsKeyWithId(recipientPub, encryptionKey.getKeyId()));
|
||||||
}
|
|
||||||
|
|
||||||
assertFalse(encryptionResult.getRecipientKeyIds().isEmpty());
|
|
||||||
for (long keyId : encryptionResult.getRecipientKeyIds()) {
|
|
||||||
assertTrue(BCUtil.keyRingContainsKeyWithId(recipientPub, keyId));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
assertEquals(SymmetricKeyAlgorithm.AES_256, encryptionResult.getSymmetricKeyAlgorithm());
|
assertEquals(SymmetricKeyAlgorithm.AES_256, encryptionResult.getSymmetricKeyAlgorithm());
|
||||||
|
@ -214,15 +209,14 @@ public class EncryptDecryptTest {
|
||||||
ByteArrayOutputStream dummyOut = new ByteArrayOutputStream();
|
ByteArrayOutputStream dummyOut = new ByteArrayOutputStream();
|
||||||
EncryptionStream signer = PGPainless.encryptAndOrSign().onOutputStream(dummyOut)
|
EncryptionStream signer = PGPainless.encryptAndOrSign().onOutputStream(dummyOut)
|
||||||
.doNotEncrypt()
|
.doNotEncrypt()
|
||||||
.createDetachedSignature()
|
.signDetachedWith(keyRingProtector, signingKeys)
|
||||||
.signWith(keyRingProtector, signingKeys)
|
|
||||||
.signBinaryDocument()
|
|
||||||
.noArmor();
|
.noArmor();
|
||||||
Streams.pipeAll(inputStream, signer);
|
Streams.pipeAll(inputStream, signer);
|
||||||
signer.close();
|
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();
|
ByteArrayOutputStream sigOut = new ByteArrayOutputStream();
|
||||||
ArmoredOutputStream armorOut = ArmoredOutputStreamFactory.get(sigOut);
|
ArmoredOutputStream armorOut = ArmoredOutputStreamFactory.get(sigOut);
|
||||||
signatureSet.iterator().next().encode(armorOut);
|
signatureSet.iterator().next().encode(armorOut);
|
||||||
|
@ -244,8 +238,8 @@ public class EncryptDecryptTest {
|
||||||
Streams.pipeAll(verifier, dummyOut);
|
Streams.pipeAll(verifier, dummyOut);
|
||||||
verifier.close();
|
verifier.close();
|
||||||
|
|
||||||
metadata = verifier.getResult();
|
OpenPgpMetadata decryptionResult = verifier.getResult();
|
||||||
assertFalse(metadata.getVerifiedSignatures().isEmpty());
|
assertFalse(decryptionResult.getVerifiedSignatures().isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
|
@ -259,8 +253,7 @@ public class EncryptDecryptTest {
|
||||||
ByteArrayOutputStream signOut = new ByteArrayOutputStream();
|
ByteArrayOutputStream signOut = new ByteArrayOutputStream();
|
||||||
EncryptionStream signer = PGPainless.encryptAndOrSign().onOutputStream(signOut)
|
EncryptionStream signer = PGPainless.encryptAndOrSign().onOutputStream(signOut)
|
||||||
.doNotEncrypt()
|
.doNotEncrypt()
|
||||||
.signWith(keyRingProtector, signingKeys)
|
.signInlineWith(keyRingProtector, signingKeys)
|
||||||
.signBinaryDocument()
|
|
||||||
.asciiArmor();
|
.asciiArmor();
|
||||||
Streams.pipeAll(inputStream, signer);
|
Streams.pipeAll(inputStream, signer);
|
||||||
signer.close();
|
signer.close();
|
||||||
|
@ -344,6 +337,6 @@ public class EncryptDecryptTest {
|
||||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
assertThrows(IllegalArgumentException.class, () ->
|
assertThrows(IllegalArgumentException.class, () ->
|
||||||
PGPainless.encryptAndOrSign().onOutputStream(outputStream)
|
PGPainless.encryptAndOrSign().onOutputStream(outputStream)
|
||||||
.toRecipients(publicKeys));
|
.toRecipient(publicKeys));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,8 +37,8 @@ public class EncryptionStreamClosedTest {
|
||||||
OutputStream out = new ByteArrayOutputStream();
|
OutputStream out = new ByteArrayOutputStream();
|
||||||
EncryptionStream stream = PGPainless.encryptAndOrSign()
|
EncryptionStream stream = PGPainless.encryptAndOrSign()
|
||||||
.onOutputStream(out)
|
.onOutputStream(out)
|
||||||
.forPassphrases(Passphrase.fromPassword("dummy"))
|
.forPassphrase(Passphrase.fromPassword("dummy"))
|
||||||
.usingSecureAlgorithms()
|
.and()
|
||||||
.doNotSign()
|
.doNotSign()
|
||||||
.asciiArmor();
|
.asciiArmor();
|
||||||
|
|
||||||
|
|
|
@ -69,8 +69,8 @@ public class FileInfoTest {
|
||||||
ByteArrayOutputStream dataOut = new ByteArrayOutputStream();
|
ByteArrayOutputStream dataOut = new ByteArrayOutputStream();
|
||||||
EncryptionStream encryptionStream = PGPainless.encryptAndOrSign()
|
EncryptionStream encryptionStream = PGPainless.encryptAndOrSign()
|
||||||
.onOutputStream(dataOut, fileInfo)
|
.onOutputStream(dataOut, fileInfo)
|
||||||
.toRecipients(publicKeys)
|
.toRecipient(publicKeys)
|
||||||
.usingSecureAlgorithms()
|
.and()
|
||||||
.doNotSign()
|
.doNotSign()
|
||||||
.noArmor();
|
.noArmor();
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,7 @@ import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||||
import org.bouncycastle.util.io.Streams;
|
import org.bouncycastle.util.io.Streams;
|
||||||
import org.pgpainless.PGPainless;
|
import org.pgpainless.PGPainless;
|
||||||
|
import org.pgpainless.algorithm.DocumentSignatureType;
|
||||||
import org.pgpainless.key.TestKeys;
|
import org.pgpainless.key.TestKeys;
|
||||||
import org.pgpainless.key.generation.type.rsa.RsaLength;
|
import org.pgpainless.key.generation.type.rsa.RsaLength;
|
||||||
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
||||||
|
@ -111,11 +112,9 @@ public class LengthTest {
|
||||||
|
|
||||||
OutputStream encryptor = PGPainless.encryptAndOrSign()
|
OutputStream encryptor = PGPainless.encryptAndOrSign()
|
||||||
.onOutputStream(envelope)
|
.onOutputStream(envelope)
|
||||||
.toRecipients(recipientPub)
|
.toRecipient(recipientPub)
|
||||||
// .doNotEncrypt()
|
.and()
|
||||||
.usingSecureAlgorithms()
|
.signInlineWith(keyDecryptor, senderSec, "simplejid@server.tld", DocumentSignatureType.BINARY_DOCUMENT)
|
||||||
.signWith(keyDecryptor, senderSec)
|
|
||||||
.signBinaryDocument()
|
|
||||||
.noArmor();
|
.noArmor();
|
||||||
|
|
||||||
Streams.pipeAll(new ByteArrayInputStream(secretMessage), encryptor);
|
Streams.pipeAll(new ByteArrayInputStream(secretMessage), encryptor);
|
||||||
|
|
|
@ -37,7 +37,7 @@ import org.bouncycastle.util.io.Streams;
|
||||||
import org.junit.jupiter.params.ParameterizedTest;
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
import org.junit.jupiter.params.provider.MethodSource;
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
import org.pgpainless.PGPainless;
|
import org.pgpainless.PGPainless;
|
||||||
import org.pgpainless.algorithm.CompressionAlgorithm;
|
import org.pgpainless.algorithm.DocumentSignatureType;
|
||||||
import org.pgpainless.algorithm.HashAlgorithm;
|
import org.pgpainless.algorithm.HashAlgorithm;
|
||||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
||||||
import org.pgpainless.decryption_verification.DecryptionStream;
|
import org.pgpainless.decryption_verification.DecryptionStream;
|
||||||
|
@ -70,10 +70,11 @@ public class SigningTest {
|
||||||
EncryptionStream encryptionStream = PGPainless.encryptAndOrSign(EncryptionStream.Purpose.STORAGE)
|
EncryptionStream encryptionStream = PGPainless.encryptAndOrSign(EncryptionStream.Purpose.STORAGE)
|
||||||
.onOutputStream(out)
|
.onOutputStream(out)
|
||||||
.toRecipients(keys)
|
.toRecipients(keys)
|
||||||
.andToSelf(KeyRingUtils.publicKeyRingFrom(cryptieKeys))
|
.and()
|
||||||
.usingAlgorithms(SymmetricKeyAlgorithm.AES_192, HashAlgorithm.SHA384, CompressionAlgorithm.ZIP)
|
.toRecipient(KeyRingUtils.publicKeyRingFrom(cryptieKeys))
|
||||||
.signWith(SecretKeyRingProtector.unlockSingleKeyWith(TestKeys.CRYPTIE_PASSPHRASE, cryptieSigningKey), cryptieKeys)
|
.and()
|
||||||
.signCanonicalText()
|
.signInlineWith(SecretKeyRingProtector.unlockSingleKeyWith(TestKeys.CRYPTIE_PASSPHRASE, cryptieSigningKey),
|
||||||
|
cryptieKeys, TestKeys.CRYPTIE_UID, DocumentSignatureType.CANONICAL_TEXT_DOCUMENT)
|
||||||
.asciiArmor();
|
.asciiArmor();
|
||||||
|
|
||||||
byte[] messageBytes = "This message is signed and encrypted to Romeo and Juliet.".getBytes(StandardCharsets.UTF_8);
|
byte[] messageBytes = "This message is signed and encrypted to Romeo and Juliet.".getBytes(StandardCharsets.UTF_8);
|
||||||
|
|
|
@ -193,8 +193,7 @@ public class ChangeSecretKeyRingPassphraseTest {
|
||||||
ByteArrayOutputStream dummy = new ByteArrayOutputStream();
|
ByteArrayOutputStream dummy = new ByteArrayOutputStream();
|
||||||
EncryptionStream stream = PGPainless.encryptAndOrSign().onOutputStream(dummy)
|
EncryptionStream stream = PGPainless.encryptAndOrSign().onOutputStream(dummy)
|
||||||
.doNotEncrypt()
|
.doNotEncrypt()
|
||||||
.signWith(PasswordBasedSecretKeyRingProtector.forKey(keyRing, passphrase), keyRing)
|
.signInlineWith(PasswordBasedSecretKeyRingProtector.forKey(keyRing, passphrase), keyRing)
|
||||||
.signBinaryDocument()
|
|
||||||
.noArmor();
|
.noArmor();
|
||||||
|
|
||||||
Streams.pipeAll(new ByteArrayInputStream(dummyMessage.getBytes()), stream);
|
Streams.pipeAll(new ByteArrayInputStream(dummyMessage.getBytes()), stream);
|
||||||
|
|
|
@ -36,7 +36,7 @@ public class MultiPassphraseSymmetricEncryptionTest {
|
||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
@MethodSource("org.pgpainless.util.TestUtil#provideImplementationFactories")
|
@MethodSource("org.pgpainless.util.TestUtil#provideImplementationFactories")
|
||||||
@Disabled
|
@Disabled
|
||||||
public void test(ImplementationFactory implementationFactory) throws IOException, PGPException {
|
public void encryptDecryptWithMultiplePassphrases(ImplementationFactory implementationFactory) throws IOException, PGPException {
|
||||||
ImplementationFactory.setFactoryImplementation(implementationFactory);
|
ImplementationFactory.setFactoryImplementation(implementationFactory);
|
||||||
String message = "Here we test if during decryption of a message that was encrypted with two passphrases, " +
|
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.";
|
"the decryptor finds the session key encrypted for the right passphrase.";
|
||||||
|
@ -44,8 +44,10 @@ public class MultiPassphraseSymmetricEncryptionTest {
|
||||||
ByteArrayOutputStream ciphertextOut = new ByteArrayOutputStream();
|
ByteArrayOutputStream ciphertextOut = new ByteArrayOutputStream();
|
||||||
EncryptionStream encryptor = PGPainless.encryptAndOrSign()
|
EncryptionStream encryptor = PGPainless.encryptAndOrSign()
|
||||||
.onOutputStream(ciphertextOut)
|
.onOutputStream(ciphertextOut)
|
||||||
.forPassphrases(Passphrase.fromPassword("p1"), Passphrase.fromPassword("p2"))
|
.forPassphrase(Passphrase.fromPassword("p1"))
|
||||||
.usingSecureAlgorithms()
|
.and()
|
||||||
|
.forPassphrase(Passphrase.fromPassword("p2"))
|
||||||
|
.and()
|
||||||
.doNotSign()
|
.doNotSign()
|
||||||
.noArmor();
|
.noArmor();
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,7 @@ import org.junit.jupiter.params.ParameterizedTest;
|
||||||
import org.junit.jupiter.params.provider.MethodSource;
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
import org.pgpainless.PGPainless;
|
import org.pgpainless.PGPainless;
|
||||||
import org.pgpainless.decryption_verification.DecryptionStream;
|
import org.pgpainless.decryption_verification.DecryptionStream;
|
||||||
|
import org.pgpainless.encryption_signing.EncryptionBuilderInterface;
|
||||||
import org.pgpainless.encryption_signing.EncryptionStream;
|
import org.pgpainless.encryption_signing.EncryptionStream;
|
||||||
import org.pgpainless.implementation.ImplementationFactory;
|
import org.pgpainless.implementation.ImplementationFactory;
|
||||||
import org.pgpainless.key.TestKeys;
|
import org.pgpainless.key.TestKeys;
|
||||||
|
@ -46,7 +47,7 @@ public class SymmetricEncryptionTest {
|
||||||
|
|
||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
@MethodSource("org.pgpainless.util.TestUtil#provideImplementationFactories")
|
@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);
|
ImplementationFactory.setFactoryImplementation(implementationFactory);
|
||||||
byte[] plaintext = "This is a secret message".getBytes(StandardCharsets.UTF_8);
|
byte[] plaintext = "This is a secret message".getBytes(StandardCharsets.UTF_8);
|
||||||
ByteArrayInputStream plaintextIn = new ByteArrayInputStream(plaintext);
|
ByteArrayInputStream plaintextIn = new ByteArrayInputStream(plaintext);
|
||||||
|
@ -54,12 +55,13 @@ public class SymmetricEncryptionTest {
|
||||||
Passphrase encryptionPassphrase = Passphrase.fromPassword("greenBeans");
|
Passphrase encryptionPassphrase = Passphrase.fromPassword("greenBeans");
|
||||||
|
|
||||||
ByteArrayOutputStream ciphertextOut = new ByteArrayOutputStream();
|
ByteArrayOutputStream ciphertextOut = new ByteArrayOutputStream();
|
||||||
EncryptionStream encryptor = PGPainless.encryptAndOrSign().onOutputStream(ciphertextOut)
|
EncryptionBuilderInterface.Armor armor = PGPainless.encryptAndOrSign().onOutputStream(ciphertextOut)
|
||||||
.forPassphrases(encryptionPassphrase)
|
.forPassphrase(encryptionPassphrase)
|
||||||
.and()
|
.and()
|
||||||
.toRecipients(encryptionKey)
|
.toRecipient(encryptionKey)
|
||||||
.usingSecureAlgorithms()
|
.and()
|
||||||
.doNotSign()
|
.doNotSign();
|
||||||
|
EncryptionStream encryptor = armor
|
||||||
.noArmor();
|
.noArmor();
|
||||||
|
|
||||||
Streams.pipeAll(plaintextIn, encryptor);
|
Streams.pipeAll(plaintextIn, encryptor);
|
||||||
|
|
|
@ -56,6 +56,6 @@ public class TestEncryptCommsStorageFlagsDifferentiated {
|
||||||
.onOutputStream(out);
|
.onOutputStream(out);
|
||||||
|
|
||||||
// since the key does not carry the flag ENCRYPT_COMMS, it cannot be used by the stream.
|
// 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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,7 @@ import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||||
import org.bouncycastle.util.io.Streams;
|
import org.bouncycastle.util.io.Streams;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.pgpainless.PGPainless;
|
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.encryption_signing.EncryptionStream;
|
||||||
import org.pgpainless.key.WeirdKeys;
|
import org.pgpainless.key.WeirdKeys;
|
||||||
import org.pgpainless.key.util.KeyRingUtils;
|
import org.pgpainless.key.util.KeyRingUtils;
|
||||||
|
@ -57,16 +57,16 @@ public class TestTwoSubkeysEncryption {
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
EncryptionStream encryptionStream = PGPainless.encryptAndOrSign(EncryptionStream.Purpose.STORAGE)
|
EncryptionStream encryptionStream = PGPainless.encryptAndOrSign(EncryptionStream.Purpose.STORAGE)
|
||||||
.onOutputStream(out)
|
.onOutputStream(out)
|
||||||
.toRecipients(publicKeys)
|
.toRecipient(publicKeys)
|
||||||
.usingSecureAlgorithms()
|
.and()
|
||||||
.doNotSign()
|
.doNotSign()
|
||||||
.noArmor();
|
.noArmor();
|
||||||
|
|
||||||
Streams.pipeAll(getPlainIn(), encryptionStream);
|
Streams.pipeAll(getPlainIn(), encryptionStream);
|
||||||
encryptionStream.close();
|
encryptionStream.close();
|
||||||
|
|
||||||
OpenPgpMetadata metadata = encryptionStream.getResult();
|
EncryptionResult metadata = encryptionStream.getResult();
|
||||||
|
|
||||||
assertEquals(2, metadata.getRecipientKeyIds().size());
|
assertEquals(2, metadata.getRecipients().size());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,17 +22,20 @@ import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Scanner;
|
import java.util.Scanner;
|
||||||
|
|
||||||
import org.bouncycastle.openpgp.PGPException;
|
import org.bouncycastle.openpgp.PGPException;
|
||||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||||
|
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
||||||
import org.bouncycastle.openpgp.PGPSecretKey;
|
import org.bouncycastle.openpgp.PGPSecretKey;
|
||||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||||
import org.bouncycastle.openpgp.PGPSignature;
|
import org.bouncycastle.openpgp.PGPSignature;
|
||||||
import org.bouncycastle.util.io.Streams;
|
import org.bouncycastle.util.io.Streams;
|
||||||
import org.pgpainless.PGPainless;
|
import org.pgpainless.PGPainless;
|
||||||
|
import org.pgpainless.algorithm.DocumentSignatureType;
|
||||||
import org.pgpainless.algorithm.KeyFlag;
|
import org.pgpainless.algorithm.KeyFlag;
|
||||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
||||||
import org.pgpainless.encryption_signing.EncryptionBuilderInterface;
|
import org.pgpainless.encryption_signing.EncryptionBuilderInterface;
|
||||||
|
@ -40,6 +43,7 @@ import org.pgpainless.encryption_signing.EncryptionStream;
|
||||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||||
import org.pgpainless.key.protection.KeyRingProtectionSettings;
|
import org.pgpainless.key.protection.KeyRingProtectionSettings;
|
||||||
import org.pgpainless.key.protection.CachingSecretKeyRingProtector;
|
import org.pgpainless.key.protection.CachingSecretKeyRingProtector;
|
||||||
|
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
||||||
import org.pgpainless.util.Passphrase;
|
import org.pgpainless.util.Passphrase;
|
||||||
import picocli.CommandLine;
|
import picocli.CommandLine;
|
||||||
|
|
||||||
|
@ -86,17 +90,26 @@ public class Encrypt implements Runnable {
|
||||||
System.exit(19);
|
System.exit(19);
|
||||||
}
|
}
|
||||||
|
|
||||||
PGPPublicKeyRing[] publicKeys = new PGPPublicKeyRing[certs.length];
|
PGPPublicKeyRing[] pubKeysArray = new PGPPublicKeyRing[certs.length];
|
||||||
for (int i = 0 ; i < certs.length; i++) {
|
for (int i = 0 ; i < certs.length; i++) {
|
||||||
try (InputStream fileIn = new FileInputStream(certs[i])) {
|
try (InputStream fileIn = new FileInputStream(certs[i])) {
|
||||||
PGPPublicKeyRing publicKey = PGPainless.readKeyRing().publicKeyRing(fileIn);
|
PGPPublicKeyRing publicKey = PGPainless.readKeyRing().publicKeyRing(fileIn);
|
||||||
publicKeys[i] = publicKey;
|
pubKeysArray[i] = publicKey;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
err_ln("Cannot read certificate " + certs[i].getName());
|
err_ln("Cannot read certificate " + certs[i].getName());
|
||||||
err_ln(e.getMessage());
|
err_ln(e.getMessage());
|
||||||
System.exit(1);
|
System.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
PGPPublicKeyRingCollection publicKeys;
|
||||||
|
try {
|
||||||
|
publicKeys = new PGPPublicKeyRingCollection(Arrays.asList(pubKeysArray));
|
||||||
|
} catch (IOException | PGPException e) {
|
||||||
|
err_ln("Cannot construct public key collection.");
|
||||||
|
err_ln(e.getMessage());
|
||||||
|
System.exit(1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
PGPSecretKeyRing[] secretKeys = new PGPSecretKeyRing[signWith.length];
|
PGPSecretKeyRing[] secretKeys = new PGPSecretKeyRing[signWith.length];
|
||||||
for (int i = 0; i < signWith.length; i++) {
|
for (int i = 0; i < signWith.length; i++) {
|
||||||
try (FileInputStream fileIn = new FileInputStream(signWith[i])) {
|
try (FileInputStream fileIn = new FileInputStream(signWith[i])) {
|
||||||
|
@ -140,25 +153,32 @@ public class Encrypt implements Runnable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
EncryptionBuilderInterface.DetachedSign builder = PGPainless.encryptAndOrSign()
|
EncryptionBuilderInterface.ToRecipientsOrSign builder = PGPainless.encryptAndOrSign()
|
||||||
.onOutputStream(System.out)
|
.onOutputStream(System.out)
|
||||||
.toRecipients(publicKeys)
|
.toRecipients(publicKeys)
|
||||||
.and()
|
.and();
|
||||||
.forPassphrases(passphraseArray)
|
for (Passphrase passphrase : passphraseArray) {
|
||||||
.usingSecureAlgorithms();
|
builder = builder.forPassphrase(passphrase).and();
|
||||||
EncryptionBuilderInterface.Armor builder_armor;
|
|
||||||
if (signWith.length != 0) {
|
|
||||||
EncryptionBuilderInterface.DocumentType documentType = builder.signWith(new CachingSecretKeyRingProtector(passphraseMap,
|
|
||||||
KeyRingProtectionSettings.secureDefaultSettings(), null), secretKeys);
|
|
||||||
if (type == Type.text || type == Type.mime) {
|
|
||||||
builder_armor = documentType.signCanonicalText();
|
|
||||||
} else {
|
|
||||||
builder_armor = documentType.signBinaryDocument();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
builder_armor = builder.doNotSign();
|
|
||||||
}
|
}
|
||||||
|
EncryptionBuilderInterface.Armor builder_armor = null;
|
||||||
|
EncryptionBuilderInterface.SignWith builder1 = builder;
|
||||||
try {
|
try {
|
||||||
|
if (signWith.length != 0) {
|
||||||
|
for (int i = 0; i < signWith.length; i++) {
|
||||||
|
PGPSecretKeyRing secretKeyRing = secretKeys[i];
|
||||||
|
EncryptionBuilderInterface.AdditionalSignWith additionalSignWith = builder1.signInlineWith(
|
||||||
|
SecretKeyRingProtector.unlockAllKeysWith(
|
||||||
|
passphraseMap.get(secretKeyRing.getPublicKey().getKeyID()),
|
||||||
|
secretKeyRing),
|
||||||
|
secretKeyRing, null,
|
||||||
|
type == Type.text || type == Type.mime ?
|
||||||
|
DocumentSignatureType.CANONICAL_TEXT_DOCUMENT : DocumentSignatureType.BINARY_DOCUMENT);
|
||||||
|
builder_armor = additionalSignWith;
|
||||||
|
builder1 = additionalSignWith.and();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
builder_armor = builder.doNotSign();
|
||||||
|
}
|
||||||
EncryptionStream encryptionStream = !armor ? builder_armor.noArmor() : builder_armor.asciiArmor();
|
EncryptionStream encryptionStream = !armor ? builder_armor.noArmor() : builder_armor.asciiArmor();
|
||||||
|
|
||||||
Streams.pipeAll(System.in, encryptionStream);
|
Streams.pipeAll(System.in, encryptionStream);
|
||||||
|
|
Loading…
Reference in a new issue