mirror of
https://github.com/pgpainless/pgpainless.git
synced 2024-12-22 19:08:00 +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.
|
||||
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.
|
||||
|
||||
|
@ -74,7 +74,7 @@ Take for example a look at this delicious key:
|
|||
|
||||
### Encrypt / Sign Data
|
||||
|
||||
Encrypting and signing data is pretty straight forward as well.
|
||||
Encrypting and signingOptions data is pretty straight forward as well.
|
||||
```java
|
||||
EncryptionStream encryptor = PGPainless.encryptAndOrSign()
|
||||
.onOutputStream(targetOuputStream)
|
||||
|
@ -102,7 +102,7 @@ Additionally you can get information about the encrypted data by calling
|
|||
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
|
||||
|
||||
|
|
|
@ -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.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPrivateKey;
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
||||
import org.bouncycastle.openpgp.PGPSecretKey;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
|
||||
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.algorithm.CompressionAlgorithm;
|
||||
import org.pgpainless.algorithm.DocumentSignatureType;
|
||||
import org.pgpainless.algorithm.HashAlgorithm;
|
||||
import org.pgpainless.algorithm.KeyFlag;
|
||||
import org.pgpainless.algorithm.SignatureType;
|
||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
||||
import org.pgpainless.decryption_verification.OpenPgpMetadata;
|
||||
import org.pgpainless.key.KeyRingValidator;
|
||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||
import org.pgpainless.key.SubkeyIdentifier;
|
||||
import org.pgpainless.exception.KeyValidationException;
|
||||
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
||||
import org.pgpainless.key.protection.UnlockSecretKey;
|
||||
import org.pgpainless.policy.Policy;
|
||||
import org.pgpainless.util.Passphrase;
|
||||
import org.pgpainless.util.Tuple;
|
||||
import org.pgpainless.util.selection.key.PublicKeySelectionStrategy;
|
||||
import org.pgpainless.util.selection.key.SecretKeySelectionStrategy;
|
||||
import org.pgpainless.util.selection.key.impl.And;
|
||||
import org.pgpainless.util.selection.key.impl.EncryptionKeySelectionStrategy;
|
||||
import org.pgpainless.util.selection.key.impl.NoRevocation;
|
||||
import org.pgpainless.util.selection.key.impl.SignatureKeySelectionStrategy;
|
||||
|
||||
public class EncryptionBuilder implements EncryptionBuilderInterface {
|
||||
|
||||
private final EncryptionStream.Purpose purpose;
|
||||
private OutputStream outputStream;
|
||||
private final Map<SubkeyIdentifier, PGPPublicKeyRing> encryptionKeys = new ConcurrentHashMap<>();
|
||||
private final Set<Passphrase> encryptionPassphrases = new HashSet<>();
|
||||
private boolean detachedSignature = false;
|
||||
private SignatureType signatureType = SignatureType.BINARY_DOCUMENT;
|
||||
private final Map<SubkeyIdentifier, PGPSecretKeyRing> signingKeys = new ConcurrentHashMap<>();
|
||||
private SecretKeyRingProtector signingKeysDecryptor;
|
||||
private SymmetricKeyAlgorithm symmetricKeyAlgorithm = SymmetricKeyAlgorithm.AES_128;
|
||||
private HashAlgorithm hashAlgorithm = HashAlgorithm.SHA256;
|
||||
private CompressionAlgorithm compressionAlgorithm = CompressionAlgorithm.UNCOMPRESSED;
|
||||
private boolean asciiArmor = false;
|
||||
private EncryptionOptions encryptionOptions;
|
||||
private SigningOptions signingOptions = new SigningOptions();
|
||||
private ProducerOptions options;
|
||||
private OpenPgpMetadata.FileInfo fileInfo;
|
||||
|
||||
public EncryptionBuilder() {
|
||||
this.purpose = EncryptionStream.Purpose.COMMUNICATIONS;
|
||||
this.encryptionOptions = new EncryptionOptions(EncryptionStream.Purpose.COMMUNICATIONS);
|
||||
}
|
||||
|
||||
public EncryptionBuilder(@Nonnull EncryptionStream.Purpose purpose) {
|
||||
this.purpose = purpose;
|
||||
this.encryptionOptions = new EncryptionOptions(purpose);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ToRecipients onOutputStream(@Nonnull OutputStream outputStream, OpenPgpMetadata.FileInfo fileInfo) {
|
||||
public ToRecipientsOrNoEncryption onOutputStream(@Nonnull OutputStream outputStream, OpenPgpMetadata.FileInfo fileInfo) {
|
||||
this.outputStream = outputStream;
|
||||
this.fileInfo = fileInfo;
|
||||
return new ToRecipientsImpl();
|
||||
return new ToRecipientsOrNoEncryptionImpl();
|
||||
}
|
||||
|
||||
class ToRecipientsImpl implements ToRecipients {
|
||||
|
||||
@Override
|
||||
public WithAlgorithms toRecipients(@Nonnull PGPPublicKeyRing... keys) {
|
||||
if (keys.length == 0) {
|
||||
throw new IllegalArgumentException("No public keys provided.");
|
||||
}
|
||||
public AdditionalRecipients toRecipient(@Nonnull PGPPublicKeyRing key) {
|
||||
encryptionOptions.addRecipient(key);
|
||||
return new AdditionalRecipientsImpl();
|
||||
}
|
||||
|
||||
Map<SubkeyIdentifier, PGPPublicKeyRing> encryptionKeys = new ConcurrentHashMap<>();
|
||||
@Override
|
||||
public AdditionalRecipients toRecipient(@Nonnull PGPPublicKeyRing key, @Nonnull String userId) {
|
||||
encryptionOptions.addRecipient(key, userId);
|
||||
return new AdditionalRecipientsImpl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AdditionalRecipients toRecipient(@Nonnull PGPPublicKeyRingCollection keys, @Nonnull String userId) {
|
||||
for (PGPPublicKeyRing ring : keys) {
|
||||
PGPPublicKeyRing validatedKeyRing = KeyRingValidator.validate(ring, PGPainless.getPolicy());
|
||||
for (PGPPublicKey k : validatedKeyRing) {
|
||||
if (encryptionKeySelector().accept(k)) {
|
||||
encryptionKeys.put(new SubkeyIdentifier(ring, k.getKeyID()), ring);
|
||||
}
|
||||
}
|
||||
encryptionOptions.addRecipient(ring, userId);
|
||||
}
|
||||
if (encryptionKeys.isEmpty()) {
|
||||
throw new IllegalArgumentException("No valid encryption keys found!");
|
||||
}
|
||||
EncryptionBuilder.this.encryptionKeys.putAll(encryptionKeys);
|
||||
|
||||
return new WithAlgorithmsImpl();
|
||||
}
|
||||
|
||||
private String getPrimaryUserId(PGPPublicKey publicKey) {
|
||||
// TODO: Use real function to get primary userId.
|
||||
return publicKey.getUserIDs().next();
|
||||
return new AdditionalRecipientsImpl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public WithAlgorithms toRecipients(@Nonnull PGPPublicKeyRingCollection... keys) {
|
||||
if (keys.length == 0) {
|
||||
throw new IllegalArgumentException("No key ring collections provided.");
|
||||
public AdditionalRecipients toRecipients(@Nonnull PGPPublicKeyRingCollection keys) {
|
||||
for (PGPPublicKeyRing ring : keys) {
|
||||
encryptionOptions.addRecipient(ring);
|
||||
}
|
||||
|
||||
for (PGPPublicKeyRingCollection collection : keys) {
|
||||
for (PGPPublicKeyRing ring : collection) {
|
||||
Map<SubkeyIdentifier, PGPPublicKeyRing> encryptionKeys = new ConcurrentHashMap<>();
|
||||
for (PGPPublicKey k : ring) {
|
||||
if (encryptionKeySelector().accept(k)) {
|
||||
encryptionKeys.put(new SubkeyIdentifier(ring, k.getKeyID()), ring);
|
||||
}
|
||||
}
|
||||
|
||||
if (encryptionKeys.isEmpty()) {
|
||||
throw new IllegalArgumentException("No valid encryption keys found!");
|
||||
}
|
||||
|
||||
EncryptionBuilder.this.encryptionKeys.putAll(encryptionKeys);
|
||||
}
|
||||
}
|
||||
|
||||
return new WithAlgorithmsImpl();
|
||||
return new AdditionalRecipientsImpl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public WithAlgorithms forPassphrases(Passphrase... passphrases) {
|
||||
List<Passphrase> passphraseList = new ArrayList<>();
|
||||
for (Passphrase passphrase : passphrases) {
|
||||
if (passphrase.isEmpty()) {
|
||||
throw new IllegalArgumentException("Passphrase must not be empty.");
|
||||
}
|
||||
passphraseList.add(passphrase);
|
||||
}
|
||||
EncryptionBuilder.this.encryptionPassphrases.addAll(passphraseList);
|
||||
return new WithAlgorithmsImpl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DetachedSign doNotEncrypt() {
|
||||
return new DetachedSignImpl();
|
||||
public AdditionalRecipients forPassphrase(Passphrase passphrase) {
|
||||
encryptionOptions.addPassphrase(passphrase);
|
||||
return new AdditionalRecipientsImpl();
|
||||
}
|
||||
}
|
||||
|
||||
class WithAlgorithmsImpl implements WithAlgorithms {
|
||||
class ToRecipientsOrNoEncryptionImpl extends ToRecipientsImpl implements ToRecipientsOrNoEncryption {
|
||||
|
||||
@Override
|
||||
public WithAlgorithms andToSelf(@Nonnull PGPPublicKeyRing... keys) {
|
||||
if (keys.length == 0) {
|
||||
throw new IllegalArgumentException("Recipient list MUST NOT be empty.");
|
||||
public EncryptionStream withOptions(ProducerOptions options) throws PGPException, IOException {
|
||||
if (options == null) {
|
||||
throw new NullPointerException("ProducerOptions cannot be null.");
|
||||
}
|
||||
for (PGPPublicKeyRing ring : keys) {
|
||||
Map<SubkeyIdentifier, PGPPublicKeyRing> encryptionKeys = new ConcurrentHashMap<>();
|
||||
for (Iterator<PGPPublicKey> i = ring.getPublicKeys(); i.hasNext(); ) {
|
||||
PGPPublicKey key = i.next();
|
||||
if (encryptionKeySelector().accept(key)) {
|
||||
encryptionKeys.put(new SubkeyIdentifier(ring, key.getKeyID()), ring);
|
||||
}
|
||||
}
|
||||
if (encryptionKeys.isEmpty()) {
|
||||
throw new IllegalArgumentException("No suitable encryption key found in the key ring " + new OpenPgpV4Fingerprint(ring));
|
||||
}
|
||||
EncryptionBuilder.this.encryptionKeys.putAll(encryptionKeys);
|
||||
}
|
||||
return this;
|
||||
return new EncryptionStream(outputStream, options, fileInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WithAlgorithms andToSelf(@Nonnull PGPPublicKeyRingCollection keys) {
|
||||
for (PGPPublicKeyRing ring : keys) {
|
||||
Map<SubkeyIdentifier, PGPPublicKeyRing> encryptionKeys = new ConcurrentHashMap<>();
|
||||
for (Iterator<PGPPublicKey> i = ring.getPublicKeys(); i.hasNext(); ) {
|
||||
PGPPublicKey key = i.next();
|
||||
if (encryptionKeySelector().accept(key)) {
|
||||
encryptionKeys.put(new SubkeyIdentifier(ring, key.getKeyID()), ring);
|
||||
}
|
||||
}
|
||||
if (encryptionKeys.isEmpty()) {
|
||||
throw new IllegalArgumentException("No suitable encryption key found in the key ring " + new OpenPgpV4Fingerprint(ring));
|
||||
}
|
||||
EncryptionBuilder.this.encryptionKeys.putAll(encryptionKeys);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DetachedSign usingAlgorithms(@Nonnull SymmetricKeyAlgorithm symmetricKeyAlgorithm,
|
||||
@Nonnull HashAlgorithm hashAlgorithm,
|
||||
@Nonnull CompressionAlgorithm compressionAlgorithm) {
|
||||
|
||||
EncryptionBuilder.this.symmetricKeyAlgorithm = symmetricKeyAlgorithm;
|
||||
EncryptionBuilder.this.hashAlgorithm = hashAlgorithm;
|
||||
EncryptionBuilder.this.compressionAlgorithm = compressionAlgorithm;
|
||||
|
||||
return new DetachedSignImpl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DetachedSign usingSecureAlgorithms() {
|
||||
EncryptionBuilder.this.symmetricKeyAlgorithm = SymmetricKeyAlgorithm.AES_256;
|
||||
EncryptionBuilder.this.hashAlgorithm = HashAlgorithm.SHA512;
|
||||
EncryptionBuilder.this.compressionAlgorithm = CompressionAlgorithm.UNCOMPRESSED;
|
||||
|
||||
return new DetachedSignImpl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ToRecipients and() {
|
||||
return new ToRecipientsImpl();
|
||||
public SignWithOrDontSign doNotEncrypt() {
|
||||
EncryptionBuilder.this.encryptionOptions = null;
|
||||
return new SignWithOrDontSignImpl();
|
||||
}
|
||||
}
|
||||
|
||||
class DetachedSignImpl implements DetachedSign {
|
||||
class AdditionalRecipientsImpl implements AdditionalRecipients {
|
||||
@Override
|
||||
public ToRecipientsOrSign and() {
|
||||
return new ToRecipientsOrSignImpl();
|
||||
}
|
||||
}
|
||||
|
||||
class ToRecipientsOrSignImpl extends ToRecipientsImpl implements ToRecipientsOrSign {
|
||||
|
||||
@Override
|
||||
public SignWith createDetachedSignature() {
|
||||
EncryptionBuilder.this.detachedSignature = true;
|
||||
return new SignWithImpl();
|
||||
public Armor doNotSign() {
|
||||
EncryptionBuilder.this.signingOptions = null;
|
||||
return new ArmorImpl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AdditionalSignWith signWith(@Nonnull SecretKeyRingProtector decryptor, @Nonnull PGPSecretKeyRing... keyRings) throws KeyValidationException {
|
||||
return new SignWithImpl().signWith(decryptor, keyRings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AdditionalSignWith signWith(@Nonnull SecretKeyRingProtector decryptor, @Nonnull PGPSecretKeyRingCollection keyRings) {
|
||||
return new SignWithImpl().signWith(decryptor, keyRings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AdditionalSignWith signInlineWith(@Nonnull SecretKeyRingProtector secretKeyDecryptor, @Nonnull PGPSecretKeyRing signingKey, String userId, DocumentSignatureType signatureType) throws PGPException {
|
||||
return new SignWithImpl().signInlineWith(secretKeyDecryptor, signingKey, userId, signatureType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AdditionalSignWith signDetachedWith(@Nonnull SecretKeyRingProtector secretKeyDecryptor, @Nonnull PGPSecretKeyRing signingKey, String userId, DocumentSignatureType signatureType) throws PGPException {
|
||||
return new SignWithImpl().signDetachedWith(secretKeyDecryptor, signingKey, userId, signatureType);
|
||||
}
|
||||
}
|
||||
|
||||
class SignWithOrDontSignImpl extends SignWithImpl implements SignWithOrDontSign {
|
||||
|
||||
@Override
|
||||
public Armor doNotSign() {
|
||||
return new ArmorImpl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DocumentType signWith(@Nonnull SecretKeyRingProtector decryptor, @Nonnull PGPSecretKeyRing... keyRings) {
|
||||
return new SignWithImpl().signWith(decryptor, keyRings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DocumentType signWith(@Nonnull SecretKeyRingProtector decryptor, @Nonnull PGPSecretKeyRingCollection keyRings) {
|
||||
return new SignWithImpl().signWith(decryptor, keyRings);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class SignWithImpl implements SignWith {
|
||||
|
||||
@Override
|
||||
public DocumentType signWith(@Nonnull SecretKeyRingProtector decryptor,
|
||||
@Nonnull PGPSecretKeyRing... keyRings) {
|
||||
if (keyRings.length == 0) {
|
||||
throw new IllegalArgumentException("Signing key list MUST NOT be empty.");
|
||||
public AdditionalSignWith signWith(@Nonnull SecretKeyRingProtector decryptor,
|
||||
@Nonnull PGPSecretKeyRing... keyRings)
|
||||
throws KeyValidationException {
|
||||
for (PGPSecretKeyRing secretKeyRing : keyRings) {
|
||||
signingOptions.addInlineSignature(decryptor, secretKeyRing, DocumentSignatureType.BINARY_DOCUMENT);
|
||||
}
|
||||
for (PGPSecretKeyRing ring : keyRings) {
|
||||
Map<SubkeyIdentifier, PGPSecretKeyRing> signingKeys = new ConcurrentHashMap<>();
|
||||
for (Iterator<PGPSecretKey> i = ring.getSecretKeys(); i.hasNext(); ) {
|
||||
PGPSecretKey s = i.next();
|
||||
if (EncryptionBuilder.this.signingKeySelector().accept(s)) {
|
||||
signingKeys.put(new SubkeyIdentifier(ring, s.getKeyID()), ring);
|
||||
}
|
||||
}
|
||||
|
||||
if (signingKeys.isEmpty()) {
|
||||
throw new IllegalArgumentException("No suitable signing key found in the key ring " + new OpenPgpV4Fingerprint(ring));
|
||||
}
|
||||
|
||||
EncryptionBuilder.this.signingKeys.putAll(signingKeys);
|
||||
}
|
||||
EncryptionBuilder.this.signingKeysDecryptor = decryptor;
|
||||
return new DocumentTypeImpl();
|
||||
return new AdditionalSignWithImpl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DocumentType signWith(@Nonnull SecretKeyRingProtector decryptor, @Nonnull PGPSecretKeyRingCollection keyRings) {
|
||||
Iterator<PGPSecretKeyRing> iterator = keyRings.iterator();
|
||||
if (!iterator.hasNext()) {
|
||||
throw new IllegalArgumentException("Signing key collection MUST NOT be empty.");
|
||||
public AdditionalSignWith signWith(@Nonnull SecretKeyRingProtector decryptor, @Nonnull PGPSecretKeyRingCollection keyRings)
|
||||
throws KeyValidationException {
|
||||
for (PGPSecretKeyRing key : keyRings) {
|
||||
signingOptions.addInlineSignature(decryptor, key, DocumentSignatureType.BINARY_DOCUMENT);
|
||||
}
|
||||
while (iterator.hasNext()) {
|
||||
PGPSecretKeyRing ring = iterator.next();
|
||||
Map<SubkeyIdentifier, PGPSecretKeyRing> signingKeys = new ConcurrentHashMap<>();
|
||||
for (Iterator<PGPSecretKey> i = ring.getSecretKeys(); i.hasNext(); ) {
|
||||
PGPSecretKey s = i.next();
|
||||
if (EncryptionBuilder.this.signingKeySelector().accept(s)) {
|
||||
signingKeys.put(new SubkeyIdentifier(ring, s.getKeyID()), ring);
|
||||
}
|
||||
}
|
||||
return new AdditionalSignWithImpl();
|
||||
}
|
||||
|
||||
if (signingKeys.isEmpty()) {
|
||||
throw new IllegalArgumentException("No suitable signing key found in the key ring " + new OpenPgpV4Fingerprint(ring));
|
||||
}
|
||||
@Override
|
||||
public AdditionalSignWith signInlineWith(@Nonnull SecretKeyRingProtector secretKeyDecryptor,
|
||||
@Nonnull PGPSecretKeyRing signingKey,
|
||||
String userId,
|
||||
DocumentSignatureType signatureType)
|
||||
throws KeyValidationException, PGPException {
|
||||
signingOptions.addInlineSignature(secretKeyDecryptor, signingKey, userId, signatureType);
|
||||
return new AdditionalSignWithImpl();
|
||||
}
|
||||
|
||||
EncryptionBuilder.this.signingKeys.putAll(signingKeys);
|
||||
}
|
||||
|
||||
EncryptionBuilder.this.signingKeysDecryptor = decryptor;
|
||||
return new DocumentTypeImpl();
|
||||
@Override
|
||||
public AdditionalSignWith signDetachedWith(@Nonnull SecretKeyRingProtector secretKeyDecryptor,
|
||||
@Nonnull PGPSecretKeyRing signingKey,
|
||||
String userId,
|
||||
DocumentSignatureType signatureType)
|
||||
throws PGPException, KeyValidationException {
|
||||
signingOptions.addInlineSignature(secretKeyDecryptor, signingKey, userId, signatureType);
|
||||
return new AdditionalSignWithImpl();
|
||||
}
|
||||
}
|
||||
|
||||
class DocumentTypeImpl implements DocumentType {
|
||||
class AdditionalSignWithImpl implements AdditionalSignWith {
|
||||
|
||||
@Override
|
||||
public Armor signBinaryDocument() {
|
||||
EncryptionBuilder.this.signatureType = SignatureType.BINARY_DOCUMENT;
|
||||
return new ArmorImpl();
|
||||
public SignWith and() {
|
||||
return new SignWithImpl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Armor signCanonicalText() {
|
||||
EncryptionBuilder.this.signatureType = SignatureType.CANONICAL_TEXT_DOCUMENT;
|
||||
return new ArmorImpl();
|
||||
public EncryptionStream asciiArmor() throws IOException, PGPException {
|
||||
return new ArmorImpl().asciiArmor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public EncryptionStream noArmor() throws IOException, PGPException {
|
||||
return new ArmorImpl().noArmor();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -329,70 +220,87 @@ public class EncryptionBuilder implements EncryptionBuilderInterface {
|
|||
|
||||
@Override
|
||||
public EncryptionStream asciiArmor() throws IOException, PGPException {
|
||||
EncryptionBuilder.this.asciiArmor = true;
|
||||
assignProducerOptions();
|
||||
options.setAsciiArmor(true);
|
||||
return build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public EncryptionStream noArmor() throws IOException, PGPException {
|
||||
EncryptionBuilder.this.asciiArmor = false;
|
||||
assignProducerOptions();
|
||||
options.setAsciiArmor(false);
|
||||
return build();
|
||||
}
|
||||
|
||||
private EncryptionStream build() throws IOException, PGPException {
|
||||
|
||||
Map<SubkeyIdentifier, Tuple<PGPSecretKeyRing, PGPPrivateKey>> privateKeys = new ConcurrentHashMap<>();
|
||||
for (SubkeyIdentifier signingKey : signingKeys.keySet()) {
|
||||
PGPSecretKeyRing secretKeyRing = signingKeys.get(signingKey);
|
||||
PGPSecretKey secretKey = secretKeyRing.getSecretKey(signingKey.getSubkeyFingerprint().getKeyId());
|
||||
PBESecretKeyDecryptor decryptor = signingKeysDecryptor.getDecryptor(secretKey.getKeyID());
|
||||
PGPPrivateKey privateKey = UnlockSecretKey.unlockSecretKey(secretKey, decryptor);
|
||||
privateKeys.put(signingKey, new Tuple<>(secretKeyRing, privateKey));
|
||||
}
|
||||
|
||||
return new EncryptionStream(
|
||||
EncryptionBuilder.this.outputStream,
|
||||
EncryptionBuilder.this.encryptionKeys,
|
||||
EncryptionBuilder.this.encryptionPassphrases,
|
||||
EncryptionBuilder.this.detachedSignature,
|
||||
signatureType,
|
||||
privateKeys,
|
||||
EncryptionBuilder.this.symmetricKeyAlgorithm,
|
||||
EncryptionBuilder.this.hashAlgorithm,
|
||||
EncryptionBuilder.this.compressionAlgorithm,
|
||||
EncryptionBuilder.this.asciiArmor,
|
||||
EncryptionBuilder.this.options,
|
||||
fileInfo);
|
||||
}
|
||||
}
|
||||
|
||||
PublicKeySelectionStrategy encryptionKeySelector() {
|
||||
KeyFlag[] flags = mapPurposeToKeyFlags(purpose);
|
||||
return new And.PubKeySelectionStrategy(
|
||||
new NoRevocation.PubKeySelectionStrategy(),
|
||||
new EncryptionKeySelectionStrategy(flags));
|
||||
}
|
||||
|
||||
SecretKeySelectionStrategy signingKeySelector() {
|
||||
return new And.SecKeySelectionStrategy(
|
||||
new NoRevocation.SecKeySelectionStrategy(),
|
||||
new SignatureKeySelectionStrategy());
|
||||
}
|
||||
|
||||
private static KeyFlag[] mapPurposeToKeyFlags(EncryptionStream.Purpose purpose) {
|
||||
KeyFlag[] flags;
|
||||
switch (purpose) {
|
||||
case COMMUNICATIONS:
|
||||
flags = new KeyFlag[] {KeyFlag.ENCRYPT_COMMS};
|
||||
break;
|
||||
case STORAGE:
|
||||
flags = new KeyFlag[] {KeyFlag.ENCRYPT_STORAGE};
|
||||
break;
|
||||
case STORAGE_AND_COMMUNICATIONS:
|
||||
flags = new KeyFlag[] {KeyFlag.ENCRYPT_COMMS, KeyFlag.ENCRYPT_STORAGE};
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError("Illegal purpose enum value encountered.");
|
||||
private void assignProducerOptions() {
|
||||
if (encryptionOptions != null && signingOptions != null) {
|
||||
options = ProducerOptions.signAndEncrypt(encryptionOptions, signingOptions);
|
||||
} else if (encryptionOptions != null) {
|
||||
options = ProducerOptions.encrypt(encryptionOptions);
|
||||
} else if (signingOptions != null) {
|
||||
options = ProducerOptions.sign(signingOptions);
|
||||
} else {
|
||||
options = ProducerOptions.noEncryptionNoSigning();
|
||||
}
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Negotiate the {@link SymmetricKeyAlgorithm} used for message encryption.
|
||||
* If the user chose to set an override ({@link EncryptionOptions#overrideEncryptionAlgorithm(SymmetricKeyAlgorithm)}, use that.
|
||||
* Otherwise find an algorithm which is acceptable for all recipients.
|
||||
* If no consensus can be reached, use {@link Policy.SymmetricKeyAlgorithmPolicy#getDefaultSymmetricKeyAlgorithm()}.
|
||||
*
|
||||
* @param encryptionOptions encryption options
|
||||
* @return negotiated symmetric key algorithm
|
||||
*/
|
||||
public static SymmetricKeyAlgorithm negotiateSymmetricEncryptionAlgorithm(EncryptionOptions encryptionOptions) {
|
||||
SymmetricKeyAlgorithm encryptionAlgorithmOverride = encryptionOptions.getEncryptionAlgorithmOverride();
|
||||
if (encryptionAlgorithmOverride != null) {
|
||||
return encryptionAlgorithmOverride;
|
||||
}
|
||||
|
||||
// TODO: Negotiation
|
||||
|
||||
return PGPainless.getPolicy().getSymmetricKeyAlgorithmPolicy().getDefaultSymmetricKeyAlgorithm();
|
||||
}
|
||||
|
||||
/**
|
||||
* Negotiate the {@link HashAlgorithm} used for signatures.
|
||||
*
|
||||
* If we encrypt and sign, we look at the recipients keys to determine which algorithm to use.
|
||||
* If we only sign, we look at the singing keys preferences instead.
|
||||
*
|
||||
* @param encryptionOptions encryption options (recipients keys)
|
||||
* @param signingOptions signing options (signing keys)
|
||||
* @return negotiated hash algorithm
|
||||
*/
|
||||
public static HashAlgorithm negotiateSignatureHashAlgorithm(EncryptionOptions encryptionOptions, SigningOptions signingOptions) {
|
||||
HashAlgorithm hashAlgorithmOverride = signingOptions.getHashAlgorithmOverride();
|
||||
if (hashAlgorithmOverride != null) {
|
||||
return hashAlgorithmOverride;
|
||||
}
|
||||
|
||||
// TODO: Negotiation
|
||||
|
||||
return PGPainless.getPolicy().getSignatureHashAlgorithmPolicy().defaultHashAlgorithm();
|
||||
}
|
||||
|
||||
public static CompressionAlgorithm negotiateCompressionAlgorithm(ProducerOptions producerOptions) {
|
||||
CompressionAlgorithm compressionAlgorithmOverride = producerOptions.getCompressionAlgorithmOverride();
|
||||
if (compressionAlgorithmOverride != null) {
|
||||
return compressionAlgorithmOverride;
|
||||
}
|
||||
|
||||
// TODO: Negotiation
|
||||
|
||||
return PGPainless.getPolicy().getCompressionAlgorithmPolicy().defaultCompressionAlgorithm();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,11 +25,10 @@ import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
|||
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
|
||||
import org.pgpainless.algorithm.CompressionAlgorithm;
|
||||
import org.pgpainless.algorithm.HashAlgorithm;
|
||||
import org.pgpainless.algorithm.DocumentSignatureType;
|
||||
import org.pgpainless.algorithm.StreamEncoding;
|
||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
||||
import org.pgpainless.decryption_verification.OpenPgpMetadata;
|
||||
import org.pgpainless.exception.KeyValidationException;
|
||||
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
||||
import org.pgpainless.util.Passphrase;
|
||||
|
||||
|
@ -42,7 +41,7 @@ public interface EncryptionBuilderInterface {
|
|||
* @param outputStream output stream of the plain data.
|
||||
* @return api handle
|
||||
*/
|
||||
default ToRecipients onOutputStream(@Nonnull OutputStream outputStream) {
|
||||
default ToRecipientsOrNoEncryption onOutputStream(@Nonnull OutputStream outputStream) {
|
||||
return onOutputStream(outputStream, OpenPgpMetadata.FileInfo.binaryStream());
|
||||
}
|
||||
/**
|
||||
|
@ -55,7 +54,7 @@ public interface EncryptionBuilderInterface {
|
|||
*
|
||||
* @deprecated use {@link #onOutputStream(OutputStream, OpenPgpMetadata.FileInfo)} instead.
|
||||
*/
|
||||
default ToRecipients onOutputStream(@Nonnull OutputStream outputStream, boolean forYourEyesOnly) {
|
||||
default ToRecipientsOrNoEncryption onOutputStream(@Nonnull OutputStream outputStream, boolean forYourEyesOnly) {
|
||||
return onOutputStream(outputStream, forYourEyesOnly ? OpenPgpMetadata.FileInfo.forYourEyesOnly() : OpenPgpMetadata.FileInfo.binaryStream());
|
||||
}
|
||||
|
||||
|
@ -70,7 +69,7 @@ public interface EncryptionBuilderInterface {
|
|||
*
|
||||
* @deprecated use {@link #onOutputStream(OutputStream, OpenPgpMetadata.FileInfo)} instead.
|
||||
*/
|
||||
default ToRecipients onOutputStream(@Nonnull OutputStream outputStream, String fileName, boolean forYourEyesOnly) {
|
||||
default ToRecipientsOrNoEncryption onOutputStream(@Nonnull OutputStream outputStream, String fileName, boolean forYourEyesOnly) {
|
||||
return onOutputStream(outputStream, new OpenPgpMetadata.FileInfo(forYourEyesOnly ? "_CONSOLE" : fileName, new Date(), StreamEncoding.BINARY));
|
||||
}
|
||||
|
||||
|
@ -82,102 +81,99 @@ public interface EncryptionBuilderInterface {
|
|||
* @param fileInfo file information
|
||||
* @return api handle
|
||||
*/
|
||||
ToRecipients onOutputStream(@Nonnull OutputStream outputStream, OpenPgpMetadata.FileInfo fileInfo);
|
||||
ToRecipientsOrNoEncryption onOutputStream(@Nonnull OutputStream outputStream, OpenPgpMetadata.FileInfo fileInfo);
|
||||
|
||||
interface ToRecipients {
|
||||
interface ToRecipientsOrNoEncryption extends ToRecipients {
|
||||
|
||||
/**
|
||||
* Pass in a list of trusted public key rings of the recipients.
|
||||
* Create an {@link EncryptionStream} with the given options (recipients, signers, algorithms...).
|
||||
*
|
||||
* @param keys recipient keys for which the message will be encrypted.
|
||||
* @return api handle
|
||||
* @param options options
|
||||
* @return encryption strea
|
||||
*/
|
||||
WithAlgorithms toRecipients(@Nonnull PGPPublicKeyRing... keys);
|
||||
|
||||
/**
|
||||
* Pass in a list of trusted public key ring collections of the recipients.
|
||||
*
|
||||
* @param keys recipient keys for which the message will be encrypted.
|
||||
* @return api handle
|
||||
*/
|
||||
WithAlgorithms toRecipients(@Nonnull PGPPublicKeyRingCollection... keys);
|
||||
|
||||
/**
|
||||
* Encrypt to one or more symmetric passphrases.
|
||||
* Note that the passphrases MUST NOT be empty.
|
||||
*
|
||||
* @param passphrases passphrase
|
||||
* @return api handle
|
||||
*/
|
||||
WithAlgorithms forPassphrases(Passphrase... passphrases);
|
||||
EncryptionStream withOptions(ProducerOptions options) throws PGPException, IOException;
|
||||
|
||||
/**
|
||||
* Instruct the {@link EncryptionStream} to not encrypt any data.
|
||||
*
|
||||
* @return api handle
|
||||
*/
|
||||
DetachedSign doNotEncrypt();
|
||||
SignWithOrDontSign doNotEncrypt();
|
||||
}
|
||||
|
||||
interface ToRecipients {
|
||||
|
||||
/**
|
||||
* Encrypt for the given valid public key.
|
||||
* TODO: Explain the difference between this and {@link #toRecipient(PGPPublicKeyRing, String)}.
|
||||
*
|
||||
* @param key recipient key for which the message will be encrypted.
|
||||
* @return api handle
|
||||
*/
|
||||
AdditionalRecipients toRecipient(@Nonnull PGPPublicKeyRing key);
|
||||
|
||||
/**
|
||||
* Encrypt for the given valid key using the provided user-id signature to determine preferences.
|
||||
*
|
||||
* @param key public key
|
||||
* @param userId user-id which is used to select the correct encryption parameters based on preferences.
|
||||
* @return api handle
|
||||
*/
|
||||
AdditionalRecipients toRecipient(@Nonnull PGPPublicKeyRing key, @Nonnull String userId);
|
||||
|
||||
/**
|
||||
* Encrypt for the first valid key in the provided keys collection which has a valid user-id that matches
|
||||
* the provided userId.
|
||||
* The user-id is also used to determine encryption preferences.
|
||||
*
|
||||
* @param keys collection of keys
|
||||
* @param userId user-id used to select the correct key
|
||||
* @return api handle
|
||||
*/
|
||||
AdditionalRecipients toRecipient(@Nonnull PGPPublicKeyRingCollection keys, @Nonnull String userId);
|
||||
|
||||
/**
|
||||
* Encrypt for all valid public keys in the provided collection.
|
||||
* If any key is not eligible for encryption (e.g. expired, revoked...), an exception will be thrown.
|
||||
* TODO: which exception?
|
||||
*
|
||||
* @param keys collection of public keys
|
||||
* @return api handle
|
||||
*/
|
||||
AdditionalRecipients toRecipients(@Nonnull PGPPublicKeyRingCollection keys);
|
||||
|
||||
/**
|
||||
* Symmetrically encrypt the message using a passphrase.
|
||||
* Note that the passphrase MUST NOT be empty.
|
||||
*
|
||||
* @param passphrase passphrase
|
||||
* @return api handle
|
||||
*/
|
||||
AdditionalRecipients forPassphrase(Passphrase passphrase);
|
||||
|
||||
}
|
||||
|
||||
interface WithAlgorithms {
|
||||
|
||||
interface AdditionalRecipients {
|
||||
/**
|
||||
* Add our own public key to the list of recipient keys.
|
||||
*
|
||||
* @param keys own public keys
|
||||
* @return api handle
|
||||
*/
|
||||
WithAlgorithms andToSelf(@Nonnull PGPPublicKeyRing... keys);
|
||||
|
||||
/**
|
||||
* Add our own public keys to the list of recipient keys.
|
||||
*
|
||||
* @param keys own public keys
|
||||
* @return api handle
|
||||
*/
|
||||
WithAlgorithms andToSelf(@Nonnull PGPPublicKeyRingCollection keys);
|
||||
|
||||
/**
|
||||
* Specify which algorithms should be used for the encryption.
|
||||
*
|
||||
* @param symmetricKeyAlgorithm symmetric algorithm for the session key
|
||||
* @param hashAlgorithm hash algorithm
|
||||
* @param compressionAlgorithm compression algorithm
|
||||
* @return api handle
|
||||
*/
|
||||
DetachedSign usingAlgorithms(@Nonnull SymmetricKeyAlgorithm symmetricKeyAlgorithm,
|
||||
@Nonnull HashAlgorithm hashAlgorithm,
|
||||
@Nonnull CompressionAlgorithm compressionAlgorithm);
|
||||
|
||||
/**
|
||||
* Use a suite of algorithms that are considered secure.
|
||||
* Add an additional recipient key/passphrase or configure signing.
|
||||
*
|
||||
* @return api handle
|
||||
*/
|
||||
DetachedSign usingSecureAlgorithms();
|
||||
|
||||
ToRecipients and();
|
||||
|
||||
ToRecipientsOrSign and();
|
||||
}
|
||||
|
||||
interface DetachedSign extends SignWith {
|
||||
|
||||
/**
|
||||
* Instruct the {@link EncryptionStream} to generate detached signatures instead of One-Pass-Signatures.
|
||||
* Those can be retrieved later via {@link OpenPgpMetadata#getSignatures()}.
|
||||
*
|
||||
* @return api handle
|
||||
*/
|
||||
SignWith createDetachedSignature();
|
||||
// Allow additional recipient or signing configuration
|
||||
interface ToRecipientsOrSign extends ToRecipients, SignWithOrDontSign {
|
||||
}
|
||||
|
||||
// Allow signing configuration or no signing at all
|
||||
interface SignWithOrDontSign extends SignWith {
|
||||
/**
|
||||
* Do not sign the plain data at all.
|
||||
*
|
||||
* @return api handle
|
||||
*/
|
||||
Armor doNotSign();
|
||||
|
||||
}
|
||||
|
||||
interface SignWith {
|
||||
|
@ -186,21 +182,104 @@ public interface EncryptionBuilderInterface {
|
|||
* Pass in a list of secret keys used for signing, along with a {@link SecretKeyRingProtector} used to unlock
|
||||
* the secret keys.
|
||||
*
|
||||
* @deprecated use {@link #signInlineWith(SecretKeyRingProtector, PGPSecretKeyRing)} instead.
|
||||
* @param decryptor {@link SecretKeyRingProtector} used to unlock the secret keys
|
||||
* @param keyRings secret keys used for signing
|
||||
* @return api handle
|
||||
*/
|
||||
DocumentType signWith(@Nonnull SecretKeyRingProtector decryptor, @Nonnull PGPSecretKeyRing... keyRings);
|
||||
@Deprecated
|
||||
AdditionalSignWith signWith(@Nonnull SecretKeyRingProtector decryptor, @Nonnull PGPSecretKeyRing... keyRings) throws KeyValidationException;
|
||||
|
||||
DocumentType signWith(@Nonnull SecretKeyRingProtector decryptor, @Nonnull PGPSecretKeyRingCollection keyRings);
|
||||
/**
|
||||
* Sign inline using the passed in secret keys.
|
||||
*
|
||||
* @deprecated use {@link #signInlineWith(SecretKeyRingProtector, PGPSecretKeyRing)} instead.
|
||||
* @param decryptor for unlocking the secret keys
|
||||
* @param keyRings secret keys
|
||||
* @return api handle
|
||||
*/
|
||||
@Deprecated
|
||||
AdditionalSignWith signWith(@Nonnull SecretKeyRingProtector decryptor, @Nonnull PGPSecretKeyRingCollection keyRings) throws KeyValidationException;
|
||||
|
||||
/**
|
||||
* Create an inline signature using the provided secret key.
|
||||
* The signature will be of type {@link DocumentSignatureType#BINARY_DOCUMENT}.
|
||||
*
|
||||
* @param secretKeyDecryptor for unlocking the secret key
|
||||
* @param signingKey signing key
|
||||
* @return api handle
|
||||
*/
|
||||
default AdditionalSignWith signInlineWith(@Nonnull SecretKeyRingProtector secretKeyDecryptor, @Nonnull PGPSecretKeyRing signingKey) throws PGPException, KeyValidationException {
|
||||
return signInlineWith(secretKeyDecryptor, signingKey, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an inline signature using the provided secret key.
|
||||
* If userId is not null, the preferences of the matching user-id on the key will be used for signing.
|
||||
* The signature will be of type {@link DocumentSignatureType#BINARY_DOCUMENT}.
|
||||
*
|
||||
* @param secretKeyDecryptor for unlocking the secret key
|
||||
* @param signingKey signing key
|
||||
* @param userId userId whose preferences shall be used for signing
|
||||
* @return api handle
|
||||
*/
|
||||
default AdditionalSignWith signInlineWith(@Nonnull SecretKeyRingProtector secretKeyDecryptor, @Nonnull PGPSecretKeyRing signingKey, String userId) throws PGPException, KeyValidationException {
|
||||
return signInlineWith(secretKeyDecryptor, signingKey, userId, DocumentSignatureType.BINARY_DOCUMENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an inline signature using the provided secret key with the algorithm preferences of the provided user-id.
|
||||
*
|
||||
* @param secretKeyDecryptor for unlocking the secret key
|
||||
* @param signingKey signing key
|
||||
* @param userId user-id whose preferences shall be used for signing
|
||||
* @param signatureType signature type
|
||||
* @return api handle
|
||||
*/
|
||||
AdditionalSignWith signInlineWith(@Nonnull SecretKeyRingProtector secretKeyDecryptor, @Nonnull PGPSecretKeyRing signingKey, String userId, DocumentSignatureType signatureType) throws KeyValidationException, PGPException;
|
||||
|
||||
/**
|
||||
* Create a detached signature using the provided secret key.
|
||||
*
|
||||
* @param secretKeyDecryptor for unlocking the secret key
|
||||
* @param signingKey signing key
|
||||
* @return api handle
|
||||
*/
|
||||
default AdditionalSignWith signDetachedWith(@Nonnull SecretKeyRingProtector secretKeyDecryptor, @Nonnull PGPSecretKeyRing signingKey) throws PGPException, KeyValidationException {
|
||||
return signDetachedWith(secretKeyDecryptor, signingKey, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a detached signature using the provided secret key with the algorithm preferences of the provided user-id.
|
||||
*
|
||||
* @param secretKeyDecryptor for unlocking the secret key
|
||||
* @param signingKey signing key
|
||||
* @param userId user-id whose preferences shall be used for signing
|
||||
* @return api handle
|
||||
*/
|
||||
default AdditionalSignWith signDetachedWith(@Nonnull SecretKeyRingProtector secretKeyDecryptor, @Nonnull PGPSecretKeyRing signingKey, String userId) throws PGPException, KeyValidationException {
|
||||
return signDetachedWith(secretKeyDecryptor, signingKey, userId, DocumentSignatureType.BINARY_DOCUMENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a detached signature using the provided secret key with the algorithm preferences of the provided user-id.
|
||||
*
|
||||
* @param secretKeyDecryptor for unlocking the secret key
|
||||
* @param signingKey signing key
|
||||
* @param userId user-id whose preferences shall be used for signing
|
||||
* @param signatureType type of the signature
|
||||
* @return api handle
|
||||
*/
|
||||
AdditionalSignWith signDetachedWith(@Nonnull SecretKeyRingProtector secretKeyDecryptor, @Nonnull PGPSecretKeyRing signingKey, String userId, DocumentSignatureType signatureType) throws PGPException, KeyValidationException;
|
||||
}
|
||||
|
||||
interface DocumentType {
|
||||
|
||||
Armor signBinaryDocument();
|
||||
|
||||
Armor signCanonicalText();
|
||||
interface AdditionalSignWith extends Armor {
|
||||
/**
|
||||
* Add an additional signing key/method.
|
||||
*
|
||||
* @return api handle
|
||||
*/
|
||||
SignWith and();
|
||||
}
|
||||
|
||||
interface Armor {
|
||||
|
|
|
@ -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.OutputStream;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import javax.annotation.Nonnull;
|
||||
|
@ -31,29 +27,18 @@ import org.bouncycastle.openpgp.PGPCompressedDataGenerator;
|
|||
import org.bouncycastle.openpgp.PGPEncryptedDataGenerator;
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPLiteralDataGenerator;
|
||||
import org.bouncycastle.openpgp.PGPPrivateKey;
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSignature;
|
||||
import org.bouncycastle.openpgp.PGPSignatureGenerator;
|
||||
import org.bouncycastle.openpgp.operator.PBEKeyEncryptionMethodGenerator;
|
||||
import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder;
|
||||
import org.bouncycastle.openpgp.operator.PGPDataEncryptorBuilder;
|
||||
import org.bouncycastle.openpgp.operator.PublicKeyKeyEncryptionMethodGenerator;
|
||||
import org.bouncycastle.openpgp.operator.PGPKeyEncryptionMethodGenerator;
|
||||
import org.bouncycastle.openpgp.operator.bc.BcPGPDataEncryptorBuilder;
|
||||
import org.bouncycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder;
|
||||
import org.pgpainless.algorithm.CompressionAlgorithm;
|
||||
import org.pgpainless.algorithm.HashAlgorithm;
|
||||
import org.pgpainless.algorithm.SignatureType;
|
||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
||||
import org.pgpainless.decryption_verification.OpenPgpMetadata;
|
||||
import org.pgpainless.implementation.ImplementationFactory;
|
||||
import org.pgpainless.key.SubkeyIdentifier;
|
||||
import org.pgpainless.signature.DetachedSignature;
|
||||
import org.pgpainless.util.ArmoredOutputStreamFactory;
|
||||
import org.pgpainless.util.Passphrase;
|
||||
import org.pgpainless.util.Tuple;
|
||||
|
||||
/**
|
||||
* This class is based upon Jens Neuhalfen's Bouncy-GPG PGPEncryptingStream.
|
||||
|
@ -81,70 +66,36 @@ public final class EncryptionStream extends OutputStream {
|
|||
private static final Logger LOGGER = Logger.getLogger(EncryptionStream.class.getName());
|
||||
private static final Level LEVEL = Level.FINE;
|
||||
|
||||
private final ProducerOptions options;
|
||||
private final EncryptionResult.Builder resultBuilder = EncryptionResult.builder();
|
||||
|
||||
private boolean closed = false;
|
||||
private static final int BUFFER_SIZE = 1 << 8;
|
||||
|
||||
private final SymmetricKeyAlgorithm symmetricKeyAlgorithm;
|
||||
private final HashAlgorithm hashAlgorithm;
|
||||
private final CompressionAlgorithm compressionAlgorithm;
|
||||
private final Map<SubkeyIdentifier, PGPPublicKeyRing> encryptionKeys;
|
||||
private final Set<Passphrase> encryptionPassphrases;
|
||||
private final boolean detachedSignature;
|
||||
private final SignatureType signatureType;
|
||||
private final Map<SubkeyIdentifier, Tuple<PGPSecretKeyRing, PGPPrivateKey>> signingKeys;
|
||||
private final boolean asciiArmor;
|
||||
|
||||
private final OpenPgpMetadata.Builder resultBuilder = OpenPgpMetadata.getBuilder();
|
||||
|
||||
private Map<SubkeyIdentifier, Tuple<PGPSecretKeyRing, PGPSignatureGenerator>> signatureGenerators = new ConcurrentHashMap<>();
|
||||
private boolean closed = false;
|
||||
|
||||
OutputStream outermostStream = null;
|
||||
|
||||
OutputStream outermostStream;
|
||||
private ArmoredOutputStream armorOutputStream = null;
|
||||
private OutputStream publicKeyEncryptedStream = null;
|
||||
|
||||
private PGPCompressedDataGenerator compressedDataGenerator;
|
||||
private BCPGOutputStream basicCompressionStream;
|
||||
|
||||
private PGPLiteralDataGenerator literalDataGenerator;
|
||||
private OutputStream literalDataStream;
|
||||
|
||||
EncryptionStream(@Nonnull OutputStream targetOutputStream,
|
||||
@Nonnull Map<SubkeyIdentifier, PGPPublicKeyRing> encryptionKeys,
|
||||
@Nonnull Set<Passphrase> encryptionPassphrases,
|
||||
boolean detachedSignature,
|
||||
SignatureType signatureType,
|
||||
@Nonnull Map<SubkeyIdentifier, Tuple<PGPSecretKeyRing, PGPPrivateKey>> signingKeys,
|
||||
@Nonnull SymmetricKeyAlgorithm symmetricKeyAlgorithm,
|
||||
@Nonnull HashAlgorithm hashAlgorithm,
|
||||
@Nonnull CompressionAlgorithm compressionAlgorithm,
|
||||
boolean asciiArmor,
|
||||
@Nonnull ProducerOptions options,
|
||||
@Nonnull OpenPgpMetadata.FileInfo fileInfo)
|
||||
throws IOException, PGPException {
|
||||
|
||||
this.symmetricKeyAlgorithm = symmetricKeyAlgorithm;
|
||||
this.hashAlgorithm = hashAlgorithm;
|
||||
this.compressionAlgorithm = compressionAlgorithm;
|
||||
this.encryptionKeys = Collections.unmodifiableMap(encryptionKeys);
|
||||
this.encryptionPassphrases = Collections.unmodifiableSet(encryptionPassphrases);
|
||||
this.detachedSignature = detachedSignature;
|
||||
this.signatureType = signatureType;
|
||||
this.signingKeys = Collections.unmodifiableMap(signingKeys);
|
||||
this.asciiArmor = asciiArmor;
|
||||
|
||||
this.options = options;
|
||||
outermostStream = targetOutputStream;
|
||||
|
||||
prepareArmor();
|
||||
prepareEncryption();
|
||||
prepareSigning();
|
||||
prepareCompression();
|
||||
prepareOnePassSignatures();
|
||||
prepareLiteralDataProcessing(fileInfo);
|
||||
prepareResultBuilder();
|
||||
resultBuilder.setFileInfo(fileInfo);
|
||||
}
|
||||
|
||||
private void prepareArmor() {
|
||||
if (!asciiArmor) {
|
||||
if (!options.isAsciiArmor()) {
|
||||
LOGGER.log(LEVEL, "Encryption output will be binary");
|
||||
return;
|
||||
}
|
||||
|
@ -155,14 +106,18 @@ public final class EncryptionStream extends OutputStream {
|
|||
}
|
||||
|
||||
private void prepareEncryption() throws IOException, PGPException {
|
||||
if (encryptionKeys.isEmpty() && encryptionPassphrases.isEmpty()) {
|
||||
EncryptionOptions encryptionOptions = options.getEncryptionOptions();
|
||||
if (encryptionOptions == null || encryptionOptions.getEncryptionMethods().isEmpty()) {
|
||||
// No encryption options/methods -> no encryption
|
||||
resultBuilder.setEncryptionAlgorithm(SymmetricKeyAlgorithm.NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
LOGGER.log(LEVEL, "At least one encryption key is available -> encrypt using " + symmetricKeyAlgorithm);
|
||||
SymmetricKeyAlgorithm encryptionAlgorithm = EncryptionBuilder.negotiateSymmetricEncryptionAlgorithm(encryptionOptions);
|
||||
resultBuilder.setEncryptionAlgorithm(encryptionAlgorithm);
|
||||
LOGGER.log(LEVEL, "Encrypt message using " + encryptionAlgorithm);
|
||||
PGPDataEncryptorBuilder dataEncryptorBuilder =
|
||||
ImplementationFactory.getInstance().getPGPDataEncryptorBuilder(symmetricKeyAlgorithm);
|
||||
|
||||
ImplementationFactory.getInstance().getPGPDataEncryptorBuilder(encryptionAlgorithm);
|
||||
// Simplify once https://github.com/bcgit/bc-java/pull/859 is merged
|
||||
if (dataEncryptorBuilder instanceof BcPGPDataEncryptorBuilder) {
|
||||
((BcPGPDataEncryptorBuilder) dataEncryptorBuilder).setWithIntegrityPacket(true);
|
||||
|
@ -172,46 +127,21 @@ public final class EncryptionStream extends OutputStream {
|
|||
|
||||
PGPEncryptedDataGenerator encryptedDataGenerator =
|
||||
new PGPEncryptedDataGenerator(dataEncryptorBuilder);
|
||||
|
||||
for (SubkeyIdentifier keyIdentifier : encryptionKeys.keySet()) {
|
||||
LOGGER.log(LEVEL, "Encrypt for key " + keyIdentifier);
|
||||
PGPPublicKey key = encryptionKeys.get(keyIdentifier).getPublicKey(keyIdentifier.getSubkeyFingerprint().getKeyId());
|
||||
PublicKeyKeyEncryptionMethodGenerator keyEncryption =
|
||||
ImplementationFactory.getInstance().getPublicKeyKeyEncryptionMethodGenerator(key);
|
||||
encryptedDataGenerator.addMethod(keyEncryption);
|
||||
for (PGPKeyEncryptionMethodGenerator encryptionMethod : encryptionOptions.getEncryptionMethods()) {
|
||||
encryptedDataGenerator.addMethod(encryptionMethod);
|
||||
}
|
||||
|
||||
for (Passphrase passphrase : encryptionPassphrases) {
|
||||
PBEKeyEncryptionMethodGenerator passphraseEncryption =
|
||||
ImplementationFactory.getInstance().getPBEKeyEncryptionMethodGenerator(passphrase);
|
||||
encryptedDataGenerator.addMethod(passphraseEncryption);
|
||||
for (SubkeyIdentifier recipientSubkeyIdentifier : encryptionOptions.getEncryptionKeyIdentifiers()) {
|
||||
resultBuilder.addRecipient(recipientSubkeyIdentifier);
|
||||
}
|
||||
|
||||
publicKeyEncryptedStream = encryptedDataGenerator.open(outermostStream, new byte[BUFFER_SIZE]);
|
||||
outermostStream = publicKeyEncryptedStream;
|
||||
}
|
||||
|
||||
private void prepareSigning() throws PGPException {
|
||||
if (signingKeys.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOGGER.log(LEVEL, "At least one signing key is available -> sign " + hashAlgorithm + " hash of message");
|
||||
for (SubkeyIdentifier subkeyIdentifier : signingKeys.keySet()) {
|
||||
LOGGER.log(LEVEL, "Sign using key " + subkeyIdentifier);
|
||||
|
||||
PGPPrivateKey privateKey = signingKeys.get(subkeyIdentifier).getSecond();
|
||||
PGPContentSignerBuilder contentSignerBuilder = ImplementationFactory.getInstance()
|
||||
.getPGPContentSignerBuilder(
|
||||
privateKey.getPublicKeyPacket().getAlgorithm(),
|
||||
hashAlgorithm.getAlgorithmId());
|
||||
PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder);
|
||||
signatureGenerator.init(signatureType.getCode(), privateKey);
|
||||
signatureGenerators.put(subkeyIdentifier, new Tuple<>(signingKeys.get(subkeyIdentifier).getFirst(), signatureGenerator));
|
||||
}
|
||||
}
|
||||
|
||||
private void prepareCompression() throws IOException {
|
||||
CompressionAlgorithm compressionAlgorithm = options.getCompressionAlgorithmOverride();
|
||||
resultBuilder.setCompressionAlgorithm(compressionAlgorithm);
|
||||
compressedDataGenerator = new PGPCompressedDataGenerator(
|
||||
compressionAlgorithm.getAlgorithmId());
|
||||
if (compressionAlgorithm == CompressionAlgorithm.UNCOMPRESSED) {
|
||||
|
@ -224,9 +154,19 @@ public final class EncryptionStream extends OutputStream {
|
|||
}
|
||||
|
||||
private void prepareOnePassSignatures() throws IOException, PGPException {
|
||||
for (SubkeyIdentifier identifier : signatureGenerators.keySet()) {
|
||||
PGPSignatureGenerator signatureGenerator = signatureGenerators.get(identifier).getSecond();
|
||||
signatureGenerator.generateOnePassVersion(false).encode(outermostStream);
|
||||
SigningOptions signingOptions = options.getSigningOptions();
|
||||
if (signingOptions == null || signingOptions.getSigningMethods().isEmpty()) {
|
||||
// No singing options/methods -> no signing
|
||||
return;
|
||||
}
|
||||
|
||||
for (SubkeyIdentifier identifier : signingOptions.getSigningMethods().keySet()) {
|
||||
SigningOptions.SigningMethod signingMethod = signingOptions.getSigningMethods().get(identifier);
|
||||
|
||||
if (!signingMethod.isDetached()) {
|
||||
PGPSignatureGenerator signatureGenerator = signingMethod.getSignatureGenerator();
|
||||
signatureGenerator.generateOnePassVersion(false).encode(outermostStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -238,38 +178,41 @@ public final class EncryptionStream extends OutputStream {
|
|||
fileInfo.getModificationDate(),
|
||||
new byte[BUFFER_SIZE]);
|
||||
outermostStream = literalDataStream;
|
||||
}
|
||||
|
||||
private void prepareResultBuilder() {
|
||||
for (SubkeyIdentifier recipient : encryptionKeys.keySet()) {
|
||||
resultBuilder.addRecipientKeyId(recipient.getSubkeyFingerprint().getKeyId());
|
||||
}
|
||||
resultBuilder.setSymmetricKeyAlgorithm(symmetricKeyAlgorithm);
|
||||
resultBuilder.setCompressionAlgorithm(compressionAlgorithm);
|
||||
resultBuilder.setFileInfo(fileInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(int data) throws IOException {
|
||||
outermostStream.write(data);
|
||||
SigningOptions signingOptions = options.getSigningOptions();
|
||||
if (signingOptions == null || signingOptions.getSigningMethods().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (SubkeyIdentifier signingKey : signatureGenerators.keySet()) {
|
||||
PGPSignatureGenerator signatureGenerator = signatureGenerators.get(signingKey).getSecond();
|
||||
for (SubkeyIdentifier signingKey : signingOptions.getSigningMethods().keySet()) {
|
||||
SigningOptions.SigningMethod signingMethod = signingOptions.getSigningMethods().get(signingKey);
|
||||
PGPSignatureGenerator signatureGenerator = signingMethod.getSignatureGenerator();
|
||||
byte asByte = (byte) (data & 0xff);
|
||||
signatureGenerator.update(asByte);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] buffer) throws IOException {
|
||||
public void write(@Nonnull byte[] buffer) throws IOException {
|
||||
write(buffer, 0, buffer.length);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void write(byte[] buffer, int off, int len) throws IOException {
|
||||
public void write(@Nonnull byte[] buffer, int off, int len) throws IOException {
|
||||
outermostStream.write(buffer, 0, len);
|
||||
for (SubkeyIdentifier signingKey : signatureGenerators.keySet()) {
|
||||
PGPSignatureGenerator signatureGenerator = signatureGenerators.get(signingKey).getSecond();
|
||||
SigningOptions signingOptions = options.getSigningOptions();
|
||||
if (signingOptions == null || signingOptions.getSigningMethods().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
for (SubkeyIdentifier signingKey : signingOptions.getSigningMethods().keySet()) {
|
||||
SigningOptions.SigningMethod signingMethod = signingOptions.getSigningMethods().get(signingKey);
|
||||
PGPSignatureGenerator signatureGenerator = signingMethod.getSignatureGenerator();
|
||||
signatureGenerator.update(buffer, 0, len);
|
||||
}
|
||||
}
|
||||
|
@ -314,19 +257,23 @@ public final class EncryptionStream extends OutputStream {
|
|||
}
|
||||
|
||||
private void writeSignatures() throws PGPException, IOException {
|
||||
for (SubkeyIdentifier signingKey : signatureGenerators.keySet()) {
|
||||
PGPSignatureGenerator signatureGenerator = signatureGenerators.get(signingKey).getSecond();
|
||||
SigningOptions signingOptions = options.getSigningOptions();
|
||||
if (signingOptions == null || signingOptions.getSigningMethods().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
for (SubkeyIdentifier signingKey : signingOptions.getSigningMethods().keySet()) {
|
||||
SigningOptions.SigningMethod signingMethod = signingOptions.getSigningMethods().get(signingKey);
|
||||
PGPSignatureGenerator signatureGenerator = signingMethod.getSignatureGenerator();
|
||||
PGPSignature signature = signatureGenerator.generate();
|
||||
if (!detachedSignature) {
|
||||
if (signingMethod.isDetached()) {
|
||||
resultBuilder.addDetachedSignature(signingKey, signature);
|
||||
} else {
|
||||
signature.encode(outermostStream);
|
||||
}
|
||||
DetachedSignature detachedSignature = new DetachedSignature(
|
||||
signature, signatureGenerators.get(signingKey).getFirst(), signingKey);
|
||||
resultBuilder.addDetachedSignature(detachedSignature);
|
||||
}
|
||||
}
|
||||
|
||||
public OpenPgpMetadata getResult() {
|
||||
public EncryptionResult getResult() {
|
||||
if (!closed) {
|
||||
throw new IllegalStateException("EncryptionStream must be closed before accessing the Result.");
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
public @Nonnull OpenPgpV4Fingerprint getFingerprint() {
|
||||
return getSubkeyFingerprint();
|
||||
}
|
||||
|
||||
public long getKeyId() {
|
||||
return getSubkeyId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link OpenPgpV4Fingerprint} of the primary key of the identified key.
|
||||
* This might be the same as {@link #getSubkeyFingerprint()} if the identified subkey is the primary key.
|
||||
|
@ -72,6 +80,16 @@ public class SubkeyIdentifier {
|
|||
return primaryKeyFingerprint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the key id of the primary key of the identified key.
|
||||
* This might be the same as {@link #getSubkeyId()} if the identified subkey is the primary key.
|
||||
*
|
||||
* @return primary key id
|
||||
*/
|
||||
public long getPrimaryKeyId() {
|
||||
return getPrimaryKeyFingerprint().getKeyId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link OpenPgpV4Fingerprint} of the identified subkey.
|
||||
*
|
||||
|
@ -81,6 +99,15 @@ public class SubkeyIdentifier {
|
|||
return subkeyFingerprint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the key id of the identified subkey.
|
||||
*
|
||||
* @return subkey id
|
||||
*/
|
||||
public long getSubkeyId() {
|
||||
return getSubkeyFingerprint().getKeyId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return primaryKeyFingerprint.hashCode() * 31 + subkeyFingerprint.hashCode();
|
||||
|
|
|
@ -20,12 +20,12 @@ import static org.pgpainless.util.CollectionUtils.iteratorToList;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
|
@ -37,9 +37,14 @@ import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
|||
import org.bouncycastle.openpgp.PGPSecretKey;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSignature;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.algorithm.HashAlgorithm;
|
||||
import org.pgpainless.algorithm.KeyFlag;
|
||||
import org.pgpainless.algorithm.PublicKeyAlgorithm;
|
||||
import org.pgpainless.encryption_signing.EncryptionStream;
|
||||
import org.pgpainless.exception.KeyValidationException;
|
||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||
import org.pgpainless.policy.Policy;
|
||||
import org.pgpainless.signature.SignaturePicker;
|
||||
import org.pgpainless.signature.SignatureUtils;
|
||||
import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil;
|
||||
|
@ -52,13 +57,7 @@ public class KeyRingInfo {
|
|||
private static final Pattern PATTERN_EMAIL = Pattern.compile("[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,6}");
|
||||
|
||||
private final PGPKeyRing keys;
|
||||
|
||||
private final PGPSignature revocationSelfSignature;
|
||||
private final PGPSignature mostRecentSelfSignature;
|
||||
private final Map<String, PGPSignature> mostRecentUserIdSignatures = new ConcurrentHashMap<>();
|
||||
private final Map<String, PGPSignature> mostRecentUserIdRevocations = new ConcurrentHashMap<>();
|
||||
private final Map<Long, PGPSignature> mostRecentSubkeyBindings = new ConcurrentHashMap<>();
|
||||
private final Map<Long, PGPSignature> mostRecentSubkeyRevocations = new ConcurrentHashMap<>();
|
||||
private Signatures signatures;
|
||||
|
||||
/**
|
||||
* Evaluate the key ring at creation time of the given signature.
|
||||
|
@ -82,36 +81,7 @@ public class KeyRingInfo {
|
|||
|
||||
public KeyRingInfo(PGPKeyRing keys, Date validationDate) {
|
||||
this.keys = keys;
|
||||
|
||||
revocationSelfSignature = SignaturePicker.pickCurrentRevocationSelfSignature(keys, validationDate);
|
||||
mostRecentSelfSignature = SignaturePicker.pickCurrentDirectKeySelfSignature(keys, validationDate);
|
||||
|
||||
for (Iterator<String> it = keys.getPublicKey().getUserIDs(); it.hasNext(); ) {
|
||||
String userId = it.next();
|
||||
PGPSignature certification = SignaturePicker.pickCurrentUserIdCertificationSignature(keys, userId, validationDate);
|
||||
if (certification != null) {
|
||||
mostRecentUserIdSignatures.put(userId, certification);
|
||||
}
|
||||
PGPSignature revocation = SignaturePicker.pickCurrentUserIdRevocationSignature(keys, userId, validationDate);
|
||||
if (revocation != null) {
|
||||
mostRecentUserIdRevocations.put(userId, revocation);
|
||||
}
|
||||
}
|
||||
|
||||
Iterator<PGPPublicKey> publicKeys = keys.getPublicKeys();
|
||||
publicKeys.next(); // Skip primary key
|
||||
|
||||
while (publicKeys.hasNext()) {
|
||||
PGPPublicKey subkey = publicKeys.next();
|
||||
PGPSignature bindingSig = SignaturePicker.pickCurrentSubkeyBindingSignature(keys, subkey, validationDate);
|
||||
if (bindingSig != null) {
|
||||
mostRecentSubkeyBindings.put(subkey.getKeyID(), bindingSig);
|
||||
}
|
||||
PGPSignature bindingRevocation = SignaturePicker.pickCurrentSubkeyBindingRevocationSignature(keys, subkey, validationDate);
|
||||
if (bindingRevocation != null) {
|
||||
mostRecentSubkeyRevocations.put(subkey.getKeyID(), bindingRevocation);
|
||||
}
|
||||
}
|
||||
this.signatures = new Signatures(keys, validationDate, PGPainless.getPolicy());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -142,11 +112,11 @@ public class KeyRingInfo {
|
|||
}
|
||||
|
||||
if (publicKey == getPublicKey()) {
|
||||
return revocationSelfSignature == null;
|
||||
return signatures.primaryKeyRevocation == null;
|
||||
}
|
||||
|
||||
PGPSignature binding = mostRecentSubkeyBindings.get(keyId);
|
||||
PGPSignature revocation = mostRecentSubkeyRevocations.get(keyId);
|
||||
PGPSignature binding = signatures.subkeyBindings.get(keyId);
|
||||
PGPSignature revocation = signatures.subkeyRevocations.get(keyId);
|
||||
return binding != null && revocation == null;
|
||||
}
|
||||
|
||||
|
@ -225,7 +195,7 @@ public class KeyRingInfo {
|
|||
String primaryUserId = null;
|
||||
Date modificationDate = null;
|
||||
for (String userId : getValidUserIds()) {
|
||||
PGPSignature signature = mostRecentUserIdSignatures.get(userId);
|
||||
PGPSignature signature = signatures.userIdCertifications.get(userId);
|
||||
PrimaryUserID subpacket = SignatureSubpacketsUtil.getPrimaryUserId(signature);
|
||||
if (subpacket != null && subpacket.isPrimaryUserID()) {
|
||||
// if there are multiple primary userIDs, return most recently signed
|
||||
|
@ -235,6 +205,11 @@ public class KeyRingInfo {
|
|||
}
|
||||
}
|
||||
}
|
||||
// Workaround for keys with only one user-id but no primary user-id packet.
|
||||
if (primaryUserId == null) {
|
||||
return getValidUserIds().get(0);
|
||||
}
|
||||
|
||||
return primaryUserId;
|
||||
}
|
||||
|
||||
|
@ -261,8 +236,8 @@ public class KeyRingInfo {
|
|||
}
|
||||
|
||||
public boolean isUserIdValid(String userId) {
|
||||
PGPSignature certification = mostRecentUserIdSignatures.get(userId);
|
||||
PGPSignature revocation = mostRecentUserIdRevocations.get(userId);
|
||||
PGPSignature certification = signatures.userIdCertifications.get(userId);
|
||||
PGPSignature revocation = signatures.userIdRevocations.get(userId);
|
||||
|
||||
return certification != null && revocation == null;
|
||||
}
|
||||
|
@ -285,34 +260,35 @@ public class KeyRingInfo {
|
|||
}
|
||||
|
||||
public PGPSignature getCurrentDirectKeySelfSignature() {
|
||||
return mostRecentSelfSignature;
|
||||
return signatures.primaryKeySelfSignature;
|
||||
}
|
||||
|
||||
public PGPSignature getRevocationSelfSignature() {
|
||||
return revocationSelfSignature;
|
||||
return signatures.primaryKeyRevocation;
|
||||
}
|
||||
|
||||
public PGPSignature getCurrentUserIdCertification(String userId) {
|
||||
return mostRecentUserIdSignatures.get(userId);
|
||||
return signatures.userIdCertifications.get(userId);
|
||||
}
|
||||
|
||||
public PGPSignature getUserIdRevocation(String userId) {
|
||||
return mostRecentUserIdRevocations.get(userId);
|
||||
return signatures.userIdRevocations.get(userId);
|
||||
}
|
||||
|
||||
public PGPSignature getCurrentSubkeyBindingSignature(long keyId) {
|
||||
return mostRecentSubkeyBindings.get(keyId);
|
||||
return signatures.subkeyBindings.get(keyId);
|
||||
}
|
||||
|
||||
public PGPSignature getSubkeyRevocationSignature(long keyId) {
|
||||
return mostRecentSubkeyRevocations.get(keyId);
|
||||
return signatures.subkeyRevocations.get(keyId);
|
||||
}
|
||||
|
||||
public List<KeyFlag> getKeyFlagsOf(long keyId) {
|
||||
if (getPublicKey().getKeyID() == keyId) {
|
||||
|
||||
if (mostRecentSelfSignature != null) {
|
||||
KeyFlags flags = SignatureSubpacketsUtil.getKeyFlags(mostRecentSelfSignature);
|
||||
PGPSignature directKeySignature = getCurrentDirectKeySelfSignature();
|
||||
if (directKeySignature != null) {
|
||||
KeyFlags flags = SignatureSubpacketsUtil.getKeyFlags(directKeySignature);
|
||||
if (flags != null) {
|
||||
return KeyFlag.fromBitmask(flags.getFlags());
|
||||
}
|
||||
|
@ -320,7 +296,15 @@ public class KeyRingInfo {
|
|||
|
||||
String primaryUserId = getPrimaryUserId();
|
||||
if (primaryUserId != null) {
|
||||
KeyFlags flags = SignatureSubpacketsUtil.getKeyFlags(mostRecentUserIdSignatures.get(primaryUserId));
|
||||
KeyFlags flags = SignatureSubpacketsUtil.getKeyFlags(getCurrentUserIdCertification(primaryUserId));
|
||||
if (flags != null) {
|
||||
return KeyFlag.fromBitmask(flags.getFlags());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
PGPSignature bindingSignature = getCurrentSubkeyBindingSignature(keyId);
|
||||
if (bindingSignature != null) {
|
||||
KeyFlags flags = SignatureSubpacketsUtil.getKeyFlags(bindingSignature);
|
||||
if (flags != null) {
|
||||
return KeyFlag.fromBitmask(flags.getFlags());
|
||||
}
|
||||
|
@ -334,7 +318,7 @@ public class KeyRingInfo {
|
|||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
PGPSignature userIdCertification = mostRecentUserIdSignatures.get(userId);
|
||||
PGPSignature userIdCertification = getCurrentUserIdCertification(userId);
|
||||
if (userIdCertification == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
@ -377,12 +361,14 @@ public class KeyRingInfo {
|
|||
|
||||
private PGPSignature getMostRecentSignature() {
|
||||
Set<PGPSignature> allSignatures = new HashSet<>();
|
||||
PGPSignature mostRecentSelfSignature = getCurrentDirectKeySelfSignature();
|
||||
PGPSignature revocationSelfSignature = getRevocationSelfSignature();
|
||||
if (mostRecentSelfSignature != null) allSignatures.add(mostRecentSelfSignature);
|
||||
if (revocationSelfSignature != null) allSignatures.add(revocationSelfSignature);
|
||||
allSignatures.addAll(mostRecentUserIdSignatures.values());
|
||||
allSignatures.addAll(mostRecentUserIdRevocations.values());
|
||||
allSignatures.addAll(mostRecentSubkeyBindings.values());
|
||||
allSignatures.addAll(mostRecentSubkeyRevocations.values());
|
||||
allSignatures.addAll(signatures.userIdCertifications.values());
|
||||
allSignatures.addAll(signatures.userIdRevocations.values());
|
||||
allSignatures.addAll(signatures.subkeyBindings.values());
|
||||
allSignatures.addAll(signatures.subkeyRevocations.values());
|
||||
|
||||
PGPSignature mostRecent = null;
|
||||
for (PGPSignature signature : allSignatures) {
|
||||
|
@ -399,7 +385,7 @@ public class KeyRingInfo {
|
|||
* @return revocation date or null
|
||||
*/
|
||||
public Date getRevocationDate() {
|
||||
return revocationSelfSignature == null ? null : revocationSelfSignature.getCreationTime();
|
||||
return getRevocationSelfSignature() == null ? null : getRevocationSelfSignature().getCreationTime();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -409,8 +395,8 @@ public class KeyRingInfo {
|
|||
*/
|
||||
public Date getPrimaryKeyExpirationDate() {
|
||||
Date lastExpiration = null;
|
||||
if (mostRecentSelfSignature != null) {
|
||||
lastExpiration = SignatureUtils.getKeyExpirationDate(getCreationDate(), mostRecentSelfSignature);
|
||||
if (getCurrentDirectKeySelfSignature() != null) {
|
||||
lastExpiration = SignatureUtils.getKeyExpirationDate(getCreationDate(), getCurrentDirectKeySelfSignature());
|
||||
}
|
||||
|
||||
for (String userId : getValidUserIds()) {
|
||||
|
@ -432,7 +418,7 @@ public class KeyRingInfo {
|
|||
if (subkey == null) {
|
||||
throw new IllegalArgumentException("No subkey with fingerprint " + fingerprint + " found.");
|
||||
}
|
||||
return SignatureUtils.getKeyExpirationDate(subkey.getCreationTime(), mostRecentSubkeyBindings.get(fingerprint.getKeyId()));
|
||||
return SignatureUtils.getKeyExpirationDate(subkey.getCreationTime(), getCurrentSubkeyBindingSignature(fingerprint.getKeyId()));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -489,4 +475,130 @@ public class KeyRingInfo {
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public PGPPublicKey getEncryptionSubkey(EncryptionStream.Purpose purpose) {
|
||||
Iterator<PGPPublicKey> subkeys = keys.getPublicKeys();
|
||||
while (subkeys.hasNext()) {
|
||||
PGPPublicKey subKey = subkeys.next();
|
||||
|
||||
if (!isKeyValidlyBound(subKey.getKeyID())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!subKey.isEncryptionKey()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
List<KeyFlag> keyFlags = getKeyFlagsOf(subKey.getKeyID());
|
||||
switch (purpose) {
|
||||
case COMMUNICATIONS:
|
||||
if (keyFlags.contains(KeyFlag.ENCRYPT_COMMS)) {
|
||||
return subKey;
|
||||
}
|
||||
break;
|
||||
case STORAGE:
|
||||
if (keyFlags.contains(KeyFlag.ENCRYPT_STORAGE)) {
|
||||
return subKey;
|
||||
}
|
||||
break;
|
||||
case STORAGE_AND_COMMUNICATIONS:
|
||||
if (keyFlags.contains(KeyFlag.ENCRYPT_COMMS) || keyFlags.contains(KeyFlag.ENCRYPT_STORAGE)) {
|
||||
return subKey;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public PGPPublicKey getEncryptionSubkey(String userId, EncryptionStream.Purpose purpose) {
|
||||
if (userId != null) {
|
||||
if (!isUserIdValid(userId)) {
|
||||
throw new KeyValidationException(userId, getCurrentUserIdCertification(userId), getUserIdRevocation(userId));
|
||||
}
|
||||
}
|
||||
|
||||
return getEncryptionSubkey(purpose);
|
||||
}
|
||||
|
||||
public PGPPublicKey getSigningSubkey() {
|
||||
Iterator<PGPPublicKey> subkeys = keys.getPublicKeys();
|
||||
while (subkeys.hasNext()) {
|
||||
PGPPublicKey subKey = subkeys.next();
|
||||
|
||||
if (!isKeyValidlyBound(subKey.getKeyID())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!subKey.isEncryptionKey()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
List<KeyFlag> keyFlags = getKeyFlagsOf(subKey.getKeyID());
|
||||
if (keyFlags.contains(KeyFlag.SIGN_DATA)) {
|
||||
return subKey;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<HashAlgorithm> getPreferredHashAlgorithms(String userId, long keyID) {
|
||||
PGPSignature signature = getCurrentUserIdCertification(userId == null ? getPrimaryUserId() : userId);
|
||||
if (signature == null) {
|
||||
signature = getCurrentDirectKeySelfSignature();
|
||||
}
|
||||
if (signature == null) {
|
||||
signature = getCurrentSubkeyBindingSignature(keyID);
|
||||
}
|
||||
if (signature == null) {
|
||||
throw new IllegalStateException("No valid signature.");
|
||||
}
|
||||
return SignatureSubpacketsUtil.parsePreferredHashAlgorithms(signature);
|
||||
}
|
||||
|
||||
public static class Signatures {
|
||||
|
||||
private final PGPSignature primaryKeyRevocation;
|
||||
private final PGPSignature primaryKeySelfSignature;
|
||||
private final Map<String, PGPSignature> userIdRevocations;
|
||||
private final Map<String, PGPSignature> userIdCertifications;
|
||||
private final Map<Long, PGPSignature> subkeyRevocations;
|
||||
private final Map<Long, PGPSignature> subkeyBindings;
|
||||
|
||||
public Signatures(PGPKeyRing keyRing, Date evaluationDate, Policy policy) {
|
||||
primaryKeyRevocation = SignaturePicker.pickCurrentRevocationSelfSignature(keyRing, evaluationDate);
|
||||
primaryKeySelfSignature = SignaturePicker.pickCurrentDirectKeySelfSignature(keyRing, evaluationDate);
|
||||
userIdRevocations = new HashMap<>();
|
||||
userIdCertifications = new HashMap<>();
|
||||
subkeyRevocations = new HashMap<>();
|
||||
subkeyBindings = new HashMap<>();
|
||||
|
||||
for (Iterator<String> it = keyRing.getPublicKey().getUserIDs(); it.hasNext(); ) {
|
||||
String userId = it.next();
|
||||
PGPSignature revocation = SignaturePicker.pickCurrentUserIdRevocationSignature(keyRing, userId, evaluationDate);
|
||||
if (revocation != null) {
|
||||
userIdRevocations.put(userId, revocation);
|
||||
}
|
||||
PGPSignature certification = SignaturePicker.pickCurrentUserIdCertificationSignature(keyRing, userId, evaluationDate);
|
||||
if (certification != null) {
|
||||
userIdCertifications.put(userId, certification);
|
||||
}
|
||||
}
|
||||
|
||||
Iterator<PGPPublicKey> keys = keyRing.getPublicKeys();
|
||||
keys.next(); // Skip primary key
|
||||
while (keys.hasNext()) {
|
||||
PGPPublicKey subkey = keys.next();
|
||||
PGPSignature subkeyRevocation = SignaturePicker.pickCurrentSubkeyBindingRevocationSignature(keyRing, subkey, evaluationDate);
|
||||
if (subkeyRevocation != null) {
|
||||
subkeyRevocations.put(subkey.getKeyID(), subkeyRevocation);
|
||||
}
|
||||
PGPSignature subkeyBinding = SignaturePicker.pickCurrentSubkeyBindingSignature(keyRing, subkey, evaluationDate);
|
||||
if (subkeyBinding != null) {
|
||||
subkeyBindings.put(subkey.getKeyID(), subkeyBinding);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ import java.util.Arrays;
|
|||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.pgpainless.algorithm.CompressionAlgorithm;
|
||||
import org.pgpainless.algorithm.HashAlgorithm;
|
||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
||||
import org.pgpainless.util.NotationRegistry;
|
||||
|
@ -38,6 +39,8 @@ public final class Policy {
|
|||
SymmetricKeyAlgorithmPolicy.defaultSymmetricKeyEncryptionAlgorithmPolicy();
|
||||
private SymmetricKeyAlgorithmPolicy symmetricKeyDecryptionAlgorithmPolicy =
|
||||
SymmetricKeyAlgorithmPolicy.defaultSymmetricKeyDecryptionAlgorithmPolicy();
|
||||
private CompressionAlgorithmPolicy compressionAlgorithmPolicy =
|
||||
CompressionAlgorithmPolicy.defaultCompressionAlgorithmPolicy();
|
||||
private final NotationRegistry notationRegistry = new NotationRegistry();
|
||||
|
||||
private Policy() {
|
||||
|
@ -142,6 +145,17 @@ public final class Policy {
|
|||
this.symmetricKeyDecryptionAlgorithmPolicy = policy;
|
||||
}
|
||||
|
||||
public CompressionAlgorithmPolicy getCompressionAlgorithmPolicy() {
|
||||
return compressionAlgorithmPolicy;
|
||||
}
|
||||
|
||||
public void setCompressionAlgorithmPolicy(CompressionAlgorithmPolicy policy) {
|
||||
if (policy == null) {
|
||||
throw new NullPointerException("Compression policy cannot be null.");
|
||||
}
|
||||
this.compressionAlgorithmPolicy = policy;
|
||||
}
|
||||
|
||||
public static final class SymmetricKeyAlgorithmPolicy {
|
||||
|
||||
private final SymmetricKeyAlgorithm defaultSymmetricKeyAlgorithm;
|
||||
|
@ -297,6 +311,39 @@ public final class Policy {
|
|||
}
|
||||
}
|
||||
|
||||
public static final class CompressionAlgorithmPolicy {
|
||||
|
||||
private final CompressionAlgorithm defaultCompressionAlgorithm;
|
||||
private final List<CompressionAlgorithm> acceptableCompressionAlgorithms;
|
||||
|
||||
public CompressionAlgorithmPolicy(CompressionAlgorithm defaultCompressionAlgorithm,
|
||||
List<CompressionAlgorithm> acceptableCompressionAlgorithms) {
|
||||
this.defaultCompressionAlgorithm = defaultCompressionAlgorithm;
|
||||
this.acceptableCompressionAlgorithms = Collections.unmodifiableList(acceptableCompressionAlgorithms);
|
||||
}
|
||||
|
||||
public CompressionAlgorithm defaultCompressionAlgorithm() {
|
||||
return defaultCompressionAlgorithm;
|
||||
}
|
||||
|
||||
public boolean isAcceptable(int compressionAlgorithmTag) {
|
||||
return isAcceptable(CompressionAlgorithm.fromId(compressionAlgorithmTag));
|
||||
}
|
||||
|
||||
public boolean isAcceptable(CompressionAlgorithm compressionAlgorithm) {
|
||||
return acceptableCompressionAlgorithms.contains(compressionAlgorithm);
|
||||
}
|
||||
|
||||
public static CompressionAlgorithmPolicy defaultCompressionAlgorithmPolicy() {
|
||||
return new CompressionAlgorithmPolicy(CompressionAlgorithm.UNCOMPRESSED, Arrays.asList(
|
||||
CompressionAlgorithm.UNCOMPRESSED,
|
||||
CompressionAlgorithm.ZIP,
|
||||
CompressionAlgorithm.BZIP2,
|
||||
CompressionAlgorithm.ZLIB
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link NotationRegistry} of PGPainless.
|
||||
* The notation registry is used to decide, whether or not a Notation is known or not.
|
||||
|
|
|
@ -59,7 +59,7 @@ public abstract class SelectSignatureFromKey {
|
|||
* Criterion that checks if the signature is valid at the validation date.
|
||||
* A signature is not valid if it was created after the validation date, or if it is expired at the validation date.
|
||||
*
|
||||
* creationTime less than or equal validationDate less than expirationDate.
|
||||
* creationTime ≤ validationDate < expirationDate.
|
||||
*
|
||||
* @param validationDate validation date
|
||||
* @return criterion implementation
|
||||
|
|
|
@ -25,8 +25,11 @@ import org.bouncycastle.bcpg.sig.SignatureCreationTime;
|
|||
import org.bouncycastle.openpgp.PGPKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
import org.bouncycastle.openpgp.PGPSignature;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.algorithm.SignatureType;
|
||||
import org.pgpainless.exception.SignatureValidationException;
|
||||
import org.pgpainless.key.util.RevocationAttributes;
|
||||
import org.pgpainless.policy.Policy;
|
||||
import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil;
|
||||
import org.pgpainless.util.CollectionUtils;
|
||||
|
||||
|
@ -53,36 +56,19 @@ public class SignaturePicker {
|
|||
* @return most recent, valid key revocation signature
|
||||
*/
|
||||
public static PGPSignature pickCurrentRevocationSelfSignature(PGPKeyRing keyRing, Date validationDate) {
|
||||
Policy policy = PGPainless.getPolicy();
|
||||
PGPPublicKey primaryKey = keyRing.getPublicKey();
|
||||
|
||||
List<PGPSignature> signatures = getSortedSignaturesOfType(primaryKey, SignatureType.KEY_REVOCATION);
|
||||
PGPSignature mostCurrentValidSig = null;
|
||||
|
||||
for (PGPSignature signature : signatures) {
|
||||
if (!SelectSignatureFromKey.isWellFormed().accept(signature, primaryKey, keyRing)) {
|
||||
// Signature is not well-formed. Reject
|
||||
try {
|
||||
SignatureValidator.verifyKeyRevocationSignature(signature, primaryKey, policy, validationDate);
|
||||
} catch (SignatureValidationException e) {
|
||||
// Signature is not valid
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!SelectSignatureFromKey.isCreatedBy(keyRing.getPublicKey()).accept(signature, primaryKey, keyRing)) {
|
||||
// Revocation signature was not created by primary key
|
||||
continue;
|
||||
}
|
||||
|
||||
RevocationReason reason = SignatureSubpacketsUtil.getRevocationReason(signature);
|
||||
if (reason != null && !RevocationAttributes.Reason.isHardRevocation(reason.getRevocationReason())) {
|
||||
// reason code states soft revocation
|
||||
if (!SelectSignatureFromKey.isValidAt(validationDate).accept(signature, primaryKey, keyRing)) {
|
||||
// Soft revocation is either expired or not yet valid
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!SelectSignatureFromKey.isValidKeyRevocationSignature(primaryKey).accept(signature, primaryKey, keyRing)) {
|
||||
// sig does not check out
|
||||
continue;
|
||||
}
|
||||
|
||||
mostCurrentValidSig = signature;
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,8 @@ import java.util.Arrays;
|
|||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import org.bouncycastle.bcpg.sig.Exportable;
|
||||
import org.bouncycastle.bcpg.sig.Features;
|
||||
import org.bouncycastle.bcpg.sig.IntendedRecipientFingerprint;
|
||||
|
@ -44,7 +46,11 @@ import org.bouncycastle.openpgp.PGPSignature;
|
|||
import org.bouncycastle.openpgp.PGPSignatureList;
|
||||
import org.bouncycastle.openpgp.PGPSignatureSubpacketVector;
|
||||
import org.bouncycastle.util.encoders.Hex;
|
||||
import org.pgpainless.algorithm.CompressionAlgorithm;
|
||||
import org.pgpainless.algorithm.HashAlgorithm;
|
||||
import org.pgpainless.algorithm.KeyFlag;
|
||||
import org.pgpainless.algorithm.SignatureSubpacket;
|
||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||
import org.pgpainless.signature.SignatureUtils;
|
||||
|
||||
|
@ -200,6 +206,17 @@ public class SignatureSubpacketsUtil {
|
|||
return hashed(signature, SignatureSubpacket.preferredSymmetricAlgorithms);
|
||||
}
|
||||
|
||||
public static List<SymmetricKeyAlgorithm> parsePreferredSymmetricKeyAlgorithms(PGPSignature signature) {
|
||||
List<SymmetricKeyAlgorithm> algorithms = new ArrayList<>();
|
||||
PreferredAlgorithms preferences = getPreferredSymmetricAlgorithms(signature);
|
||||
if (preferences != null) {
|
||||
for (int code : preferences.getPreferences()) {
|
||||
algorithms.add(SymmetricKeyAlgorithm.fromId(code));
|
||||
}
|
||||
}
|
||||
return algorithms;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the hash algorithm preferences from the signatures hashed area.
|
||||
*
|
||||
|
@ -210,6 +227,17 @@ public class SignatureSubpacketsUtil {
|
|||
return hashed(signature, SignatureSubpacket.preferredHashAlgorithms);
|
||||
}
|
||||
|
||||
public static List<HashAlgorithm> parsePreferredHashAlgorithms(PGPSignature signature) {
|
||||
List<HashAlgorithm> algorithms = new ArrayList<>();
|
||||
PreferredAlgorithms preferences = getPreferredHashAlgorithms(signature);
|
||||
if (preferences != null) {
|
||||
for (int code : preferences.getPreferences()) {
|
||||
algorithms.add(HashAlgorithm.fromId(code));
|
||||
}
|
||||
}
|
||||
return algorithms;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the compression algorithm preferences from the signatures hashed area.
|
||||
*
|
||||
|
@ -220,6 +248,17 @@ public class SignatureSubpacketsUtil {
|
|||
return hashed(signature, SignatureSubpacket.preferredCompressionAlgorithms);
|
||||
}
|
||||
|
||||
public static List<CompressionAlgorithm> parsePreferredCompressionAlgorithms(PGPSignature signature) {
|
||||
List<CompressionAlgorithm> algorithms = new ArrayList<>();
|
||||
PreferredAlgorithms preferences = getPreferredCompressionAlgorithms(signature);
|
||||
if (preferences != null) {
|
||||
for (int code : preferences.getPreferences()) {
|
||||
algorithms.add(CompressionAlgorithm.fromId(code));
|
||||
}
|
||||
}
|
||||
return algorithms;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the primary user-id subpacket from the signatures hashed area.
|
||||
*
|
||||
|
@ -240,6 +279,24 @@ public class SignatureSubpacketsUtil {
|
|||
return hashed(signature, SignatureSubpacket.keyFlags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of key flags carried by the signature.
|
||||
* If the signature is null, or has no {@link KeyFlags} subpacket, return null.
|
||||
*
|
||||
* @param signature signature
|
||||
* @return list of key flags
|
||||
*/
|
||||
public static List<KeyFlag> parseKeyFlags(@Nullable PGPSignature signature) {
|
||||
if (signature == null) {
|
||||
return null;
|
||||
}
|
||||
KeyFlags keyFlags = getKeyFlags(signature);
|
||||
if (keyFlags == null) {
|
||||
return null;
|
||||
}
|
||||
return KeyFlag.fromBitmask(keyFlags.getFlags());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the features subpacket from the signatures hashed area.
|
||||
*
|
||||
|
|
|
@ -17,18 +17,23 @@ package org.bouncycastle;
|
|||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSignature;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.key.util.KeyRingUtils;
|
||||
import org.pgpainless.util.CollectionUtils;
|
||||
|
||||
public class PGPPublicKeyRingTest {
|
||||
|
||||
|
@ -57,4 +62,21 @@ public class PGPPublicKeyRingTest {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeUserIdTest() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException {
|
||||
String userId = "alice@wonderland.lit";
|
||||
PGPSecretKeyRing secretKeyRing = PGPainless.generateKeyRing().simpleEcKeyRing(userId);
|
||||
PGPPublicKeyRing publicKeys = KeyRingUtils.publicKeyRingFrom(secretKeyRing);
|
||||
|
||||
List<String> userIds = CollectionUtils.iteratorToList(publicKeys.getPublicKey().getUserIDs());
|
||||
assertTrue(userIds.contains(userId));
|
||||
|
||||
PGPPublicKey publicKey = publicKeys.getPublicKey();
|
||||
PGPSignature cert = publicKey.getSignaturesForID(userId).next();
|
||||
publicKey = PGPPublicKey.removeCertification(publicKey, cert);
|
||||
|
||||
userIds = CollectionUtils.iteratorToList(publicKey.getUserIDs());
|
||||
assertFalse(userIds.contains(userId));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,12 +41,13 @@ import org.junit.jupiter.api.Test;
|
|||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.algorithm.DocumentSignatureType;
|
||||
import org.pgpainless.algorithm.KeyFlag;
|
||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
||||
import org.pgpainless.decryption_verification.DecryptionStream;
|
||||
import org.pgpainless.decryption_verification.OpenPgpMetadata;
|
||||
import org.pgpainless.implementation.ImplementationFactory;
|
||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||
import org.pgpainless.key.SubkeyIdentifier;
|
||||
import org.pgpainless.key.TestKeys;
|
||||
import org.pgpainless.key.generation.KeySpec;
|
||||
import org.pgpainless.key.generation.type.KeyType;
|
||||
|
@ -155,26 +156,20 @@ public class EncryptDecryptTest {
|
|||
|
||||
EncryptionStream encryptor = PGPainless.encryptAndOrSign()
|
||||
.onOutputStream(envelope)
|
||||
.toRecipients(recipientPub)
|
||||
.usingSecureAlgorithms()
|
||||
.signWith(keyDecryptor, senderSec)
|
||||
.signBinaryDocument()
|
||||
.toRecipient(recipientPub)
|
||||
.and()
|
||||
.signInlineWith(keyDecryptor, senderSec, null, DocumentSignatureType.BINARY_DOCUMENT)
|
||||
.noArmor();
|
||||
|
||||
Streams.pipeAll(new ByteArrayInputStream(secretMessage), encryptor);
|
||||
encryptor.close();
|
||||
byte[] encryptedSecretMessage = envelope.toByteArray();
|
||||
|
||||
OpenPgpMetadata encryptionResult = encryptor.getResult();
|
||||
EncryptionResult encryptionResult = encryptor.getResult();
|
||||
|
||||
assertFalse(encryptionResult.getSignatures().isEmpty());
|
||||
for (OpenPgpV4Fingerprint fingerprint : encryptionResult.getVerifiedSignatures().keySet()) {
|
||||
assertTrue(BCUtil.keyRingContainsKeyWithId(senderPub, fingerprint.getKeyId()));
|
||||
}
|
||||
|
||||
assertFalse(encryptionResult.getRecipientKeyIds().isEmpty());
|
||||
for (long keyId : encryptionResult.getRecipientKeyIds()) {
|
||||
assertTrue(BCUtil.keyRingContainsKeyWithId(recipientPub, keyId));
|
||||
assertFalse(encryptionResult.getRecipients().isEmpty());
|
||||
for (SubkeyIdentifier encryptionKey : encryptionResult.getRecipients()) {
|
||||
assertTrue(BCUtil.keyRingContainsKeyWithId(recipientPub, encryptionKey.getKeyId()));
|
||||
}
|
||||
|
||||
assertEquals(SymmetricKeyAlgorithm.AES_256, encryptionResult.getSymmetricKeyAlgorithm());
|
||||
|
@ -214,15 +209,14 @@ public class EncryptDecryptTest {
|
|||
ByteArrayOutputStream dummyOut = new ByteArrayOutputStream();
|
||||
EncryptionStream signer = PGPainless.encryptAndOrSign().onOutputStream(dummyOut)
|
||||
.doNotEncrypt()
|
||||
.createDetachedSignature()
|
||||
.signWith(keyRingProtector, signingKeys)
|
||||
.signBinaryDocument()
|
||||
.signDetachedWith(keyRingProtector, signingKeys)
|
||||
.noArmor();
|
||||
Streams.pipeAll(inputStream, signer);
|
||||
signer.close();
|
||||
OpenPgpMetadata metadata = signer.getResult();
|
||||
|
||||
Set<PGPSignature> signatureSet = metadata.getSignatures();
|
||||
EncryptionResult metadata = signer.getResult();
|
||||
|
||||
Set<PGPSignature> signatureSet = metadata.getDetachedSignatures().get(metadata.getDetachedSignatures().keySet().iterator().next());
|
||||
ByteArrayOutputStream sigOut = new ByteArrayOutputStream();
|
||||
ArmoredOutputStream armorOut = ArmoredOutputStreamFactory.get(sigOut);
|
||||
signatureSet.iterator().next().encode(armorOut);
|
||||
|
@ -244,8 +238,8 @@ public class EncryptDecryptTest {
|
|||
Streams.pipeAll(verifier, dummyOut);
|
||||
verifier.close();
|
||||
|
||||
metadata = verifier.getResult();
|
||||
assertFalse(metadata.getVerifiedSignatures().isEmpty());
|
||||
OpenPgpMetadata decryptionResult = verifier.getResult();
|
||||
assertFalse(decryptionResult.getVerifiedSignatures().isEmpty());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
|
@ -259,8 +253,7 @@ public class EncryptDecryptTest {
|
|||
ByteArrayOutputStream signOut = new ByteArrayOutputStream();
|
||||
EncryptionStream signer = PGPainless.encryptAndOrSign().onOutputStream(signOut)
|
||||
.doNotEncrypt()
|
||||
.signWith(keyRingProtector, signingKeys)
|
||||
.signBinaryDocument()
|
||||
.signInlineWith(keyRingProtector, signingKeys)
|
||||
.asciiArmor();
|
||||
Streams.pipeAll(inputStream, signer);
|
||||
signer.close();
|
||||
|
@ -344,6 +337,6 @@ public class EncryptDecryptTest {
|
|||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
assertThrows(IllegalArgumentException.class, () ->
|
||||
PGPainless.encryptAndOrSign().onOutputStream(outputStream)
|
||||
.toRecipients(publicKeys));
|
||||
.toRecipient(publicKeys));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,8 +37,8 @@ public class EncryptionStreamClosedTest {
|
|||
OutputStream out = new ByteArrayOutputStream();
|
||||
EncryptionStream stream = PGPainless.encryptAndOrSign()
|
||||
.onOutputStream(out)
|
||||
.forPassphrases(Passphrase.fromPassword("dummy"))
|
||||
.usingSecureAlgorithms()
|
||||
.forPassphrase(Passphrase.fromPassword("dummy"))
|
||||
.and()
|
||||
.doNotSign()
|
||||
.asciiArmor();
|
||||
|
||||
|
|
|
@ -69,8 +69,8 @@ public class FileInfoTest {
|
|||
ByteArrayOutputStream dataOut = new ByteArrayOutputStream();
|
||||
EncryptionStream encryptionStream = PGPainless.encryptAndOrSign()
|
||||
.onOutputStream(dataOut, fileInfo)
|
||||
.toRecipients(publicKeys)
|
||||
.usingSecureAlgorithms()
|
||||
.toRecipient(publicKeys)
|
||||
.and()
|
||||
.doNotSign()
|
||||
.noArmor();
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
|||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.util.io.Streams;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.algorithm.DocumentSignatureType;
|
||||
import org.pgpainless.key.TestKeys;
|
||||
import org.pgpainless.key.generation.type.rsa.RsaLength;
|
||||
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
||||
|
@ -111,11 +112,9 @@ public class LengthTest {
|
|||
|
||||
OutputStream encryptor = PGPainless.encryptAndOrSign()
|
||||
.onOutputStream(envelope)
|
||||
.toRecipients(recipientPub)
|
||||
// .doNotEncrypt()
|
||||
.usingSecureAlgorithms()
|
||||
.signWith(keyDecryptor, senderSec)
|
||||
.signBinaryDocument()
|
||||
.toRecipient(recipientPub)
|
||||
.and()
|
||||
.signInlineWith(keyDecryptor, senderSec, "simplejid@server.tld", DocumentSignatureType.BINARY_DOCUMENT)
|
||||
.noArmor();
|
||||
|
||||
Streams.pipeAll(new ByteArrayInputStream(secretMessage), encryptor);
|
||||
|
|
|
@ -37,7 +37,7 @@ import org.bouncycastle.util.io.Streams;
|
|||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.algorithm.CompressionAlgorithm;
|
||||
import org.pgpainless.algorithm.DocumentSignatureType;
|
||||
import org.pgpainless.algorithm.HashAlgorithm;
|
||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
||||
import org.pgpainless.decryption_verification.DecryptionStream;
|
||||
|
@ -70,10 +70,11 @@ public class SigningTest {
|
|||
EncryptionStream encryptionStream = PGPainless.encryptAndOrSign(EncryptionStream.Purpose.STORAGE)
|
||||
.onOutputStream(out)
|
||||
.toRecipients(keys)
|
||||
.andToSelf(KeyRingUtils.publicKeyRingFrom(cryptieKeys))
|
||||
.usingAlgorithms(SymmetricKeyAlgorithm.AES_192, HashAlgorithm.SHA384, CompressionAlgorithm.ZIP)
|
||||
.signWith(SecretKeyRingProtector.unlockSingleKeyWith(TestKeys.CRYPTIE_PASSPHRASE, cryptieSigningKey), cryptieKeys)
|
||||
.signCanonicalText()
|
||||
.and()
|
||||
.toRecipient(KeyRingUtils.publicKeyRingFrom(cryptieKeys))
|
||||
.and()
|
||||
.signInlineWith(SecretKeyRingProtector.unlockSingleKeyWith(TestKeys.CRYPTIE_PASSPHRASE, cryptieSigningKey),
|
||||
cryptieKeys, TestKeys.CRYPTIE_UID, DocumentSignatureType.CANONICAL_TEXT_DOCUMENT)
|
||||
.asciiArmor();
|
||||
|
||||
byte[] messageBytes = "This message is signed and encrypted to Romeo and Juliet.".getBytes(StandardCharsets.UTF_8);
|
||||
|
|
|
@ -193,8 +193,7 @@ public class ChangeSecretKeyRingPassphraseTest {
|
|||
ByteArrayOutputStream dummy = new ByteArrayOutputStream();
|
||||
EncryptionStream stream = PGPainless.encryptAndOrSign().onOutputStream(dummy)
|
||||
.doNotEncrypt()
|
||||
.signWith(PasswordBasedSecretKeyRingProtector.forKey(keyRing, passphrase), keyRing)
|
||||
.signBinaryDocument()
|
||||
.signInlineWith(PasswordBasedSecretKeyRingProtector.forKey(keyRing, passphrase), keyRing)
|
||||
.noArmor();
|
||||
|
||||
Streams.pipeAll(new ByteArrayInputStream(dummyMessage.getBytes()), stream);
|
||||
|
|
|
@ -36,7 +36,7 @@ public class MultiPassphraseSymmetricEncryptionTest {
|
|||
@ParameterizedTest
|
||||
@MethodSource("org.pgpainless.util.TestUtil#provideImplementationFactories")
|
||||
@Disabled
|
||||
public void test(ImplementationFactory implementationFactory) throws IOException, PGPException {
|
||||
public void encryptDecryptWithMultiplePassphrases(ImplementationFactory implementationFactory) throws IOException, PGPException {
|
||||
ImplementationFactory.setFactoryImplementation(implementationFactory);
|
||||
String message = "Here we test if during decryption of a message that was encrypted with two passphrases, " +
|
||||
"the decryptor finds the session key encrypted for the right passphrase.";
|
||||
|
@ -44,8 +44,10 @@ public class MultiPassphraseSymmetricEncryptionTest {
|
|||
ByteArrayOutputStream ciphertextOut = new ByteArrayOutputStream();
|
||||
EncryptionStream encryptor = PGPainless.encryptAndOrSign()
|
||||
.onOutputStream(ciphertextOut)
|
||||
.forPassphrases(Passphrase.fromPassword("p1"), Passphrase.fromPassword("p2"))
|
||||
.usingSecureAlgorithms()
|
||||
.forPassphrase(Passphrase.fromPassword("p1"))
|
||||
.and()
|
||||
.forPassphrase(Passphrase.fromPassword("p2"))
|
||||
.and()
|
||||
.doNotSign()
|
||||
.noArmor();
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ import org.junit.jupiter.params.ParameterizedTest;
|
|||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.decryption_verification.DecryptionStream;
|
||||
import org.pgpainless.encryption_signing.EncryptionBuilderInterface;
|
||||
import org.pgpainless.encryption_signing.EncryptionStream;
|
||||
import org.pgpainless.implementation.ImplementationFactory;
|
||||
import org.pgpainless.key.TestKeys;
|
||||
|
@ -46,7 +47,7 @@ public class SymmetricEncryptionTest {
|
|||
|
||||
@ParameterizedTest
|
||||
@MethodSource("org.pgpainless.util.TestUtil#provideImplementationFactories")
|
||||
public void test(ImplementationFactory implementationFactory) throws IOException, PGPException {
|
||||
public void encryptWithKeyAndPassphrase_DecryptWithKey(ImplementationFactory implementationFactory) throws IOException, PGPException {
|
||||
ImplementationFactory.setFactoryImplementation(implementationFactory);
|
||||
byte[] plaintext = "This is a secret message".getBytes(StandardCharsets.UTF_8);
|
||||
ByteArrayInputStream plaintextIn = new ByteArrayInputStream(plaintext);
|
||||
|
@ -54,12 +55,13 @@ public class SymmetricEncryptionTest {
|
|||
Passphrase encryptionPassphrase = Passphrase.fromPassword("greenBeans");
|
||||
|
||||
ByteArrayOutputStream ciphertextOut = new ByteArrayOutputStream();
|
||||
EncryptionStream encryptor = PGPainless.encryptAndOrSign().onOutputStream(ciphertextOut)
|
||||
.forPassphrases(encryptionPassphrase)
|
||||
EncryptionBuilderInterface.Armor armor = PGPainless.encryptAndOrSign().onOutputStream(ciphertextOut)
|
||||
.forPassphrase(encryptionPassphrase)
|
||||
.and()
|
||||
.toRecipients(encryptionKey)
|
||||
.usingSecureAlgorithms()
|
||||
.doNotSign()
|
||||
.toRecipient(encryptionKey)
|
||||
.and()
|
||||
.doNotSign();
|
||||
EncryptionStream encryptor = armor
|
||||
.noArmor();
|
||||
|
||||
Streams.pipeAll(plaintextIn, encryptor);
|
||||
|
|
|
@ -56,6 +56,6 @@ public class TestEncryptCommsStorageFlagsDifferentiated {
|
|||
.onOutputStream(out);
|
||||
|
||||
// since the key does not carry the flag ENCRYPT_COMMS, it cannot be used by the stream.
|
||||
assertThrows(IllegalArgumentException.class, () -> builder.toRecipients(publicKeys));
|
||||
assertThrows(IllegalArgumentException.class, () -> builder.toRecipient(publicKeys));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
|||
import org.bouncycastle.util.io.Streams;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.decryption_verification.OpenPgpMetadata;
|
||||
import org.pgpainless.encryption_signing.EncryptionResult;
|
||||
import org.pgpainless.encryption_signing.EncryptionStream;
|
||||
import org.pgpainless.key.WeirdKeys;
|
||||
import org.pgpainless.key.util.KeyRingUtils;
|
||||
|
@ -57,16 +57,16 @@ public class TestTwoSubkeysEncryption {
|
|||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
EncryptionStream encryptionStream = PGPainless.encryptAndOrSign(EncryptionStream.Purpose.STORAGE)
|
||||
.onOutputStream(out)
|
||||
.toRecipients(publicKeys)
|
||||
.usingSecureAlgorithms()
|
||||
.toRecipient(publicKeys)
|
||||
.and()
|
||||
.doNotSign()
|
||||
.noArmor();
|
||||
|
||||
Streams.pipeAll(getPlainIn(), encryptionStream);
|
||||
encryptionStream.close();
|
||||
|
||||
OpenPgpMetadata metadata = encryptionStream.getResult();
|
||||
EncryptionResult metadata = encryptionStream.getResult();
|
||||
|
||||
assertEquals(2, metadata.getRecipientKeyIds().size());
|
||||
assertEquals(2, metadata.getRecipients().size());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,17 +22,20 @@ import java.io.File;
|
|||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Scanner;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
||||
import org.bouncycastle.openpgp.PGPSecretKey;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSignature;
|
||||
import org.bouncycastle.util.io.Streams;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.algorithm.DocumentSignatureType;
|
||||
import org.pgpainless.algorithm.KeyFlag;
|
||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
||||
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.protection.KeyRingProtectionSettings;
|
||||
import org.pgpainless.key.protection.CachingSecretKeyRingProtector;
|
||||
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
||||
import org.pgpainless.util.Passphrase;
|
||||
import picocli.CommandLine;
|
||||
|
||||
|
@ -86,17 +90,26 @@ public class Encrypt implements Runnable {
|
|||
System.exit(19);
|
||||
}
|
||||
|
||||
PGPPublicKeyRing[] publicKeys = new PGPPublicKeyRing[certs.length];
|
||||
PGPPublicKeyRing[] pubKeysArray = new PGPPublicKeyRing[certs.length];
|
||||
for (int i = 0 ; i < certs.length; i++) {
|
||||
try (InputStream fileIn = new FileInputStream(certs[i])) {
|
||||
PGPPublicKeyRing publicKey = PGPainless.readKeyRing().publicKeyRing(fileIn);
|
||||
publicKeys[i] = publicKey;
|
||||
pubKeysArray[i] = publicKey;
|
||||
} catch (IOException e) {
|
||||
err_ln("Cannot read certificate " + certs[i].getName());
|
||||
err_ln(e.getMessage());
|
||||
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];
|
||||
for (int i = 0; i < signWith.length; 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)
|
||||
.toRecipients(publicKeys)
|
||||
.and()
|
||||
.forPassphrases(passphraseArray)
|
||||
.usingSecureAlgorithms();
|
||||
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();
|
||||
.and();
|
||||
for (Passphrase passphrase : passphraseArray) {
|
||||
builder = builder.forPassphrase(passphrase).and();
|
||||
}
|
||||
EncryptionBuilderInterface.Armor builder_armor = null;
|
||||
EncryptionBuilderInterface.SignWith builder1 = builder;
|
||||
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();
|
||||
|
||||
Streams.pipeAll(System.in, encryptionStream);
|
||||
|
|
Loading…
Reference in a new issue