1
0
Fork 0
mirror of https://github.com/pgpainless/pgpainless.git synced 2025-12-11 06:41:09 +01:00

Wip: SOP 4

This commit is contained in:
Paul Schaub 2022-06-07 08:55:10 +02:00
parent 9cdea63ec4
commit 9a545a2936
16 changed files with 429 additions and 58 deletions

View file

@ -7,9 +7,12 @@ package org.pgpainless.sop;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
@ -23,9 +26,8 @@ import org.pgpainless.decryption_verification.ConsumerOptions;
import org.pgpainless.decryption_verification.DecryptionStream;
import org.pgpainless.decryption_verification.OpenPgpMetadata;
import org.pgpainless.exception.MissingDecryptionMethodException;
import org.pgpainless.exception.WrongPassphraseException;
import org.pgpainless.key.SubkeyIdentifier;
import org.pgpainless.key.info.KeyRingInfo;
import org.pgpainless.key.protection.SecretKeyRingProtector;
import org.pgpainless.util.Passphrase;
import sop.DecryptionResult;
import sop.ReadyWithResult;
@ -37,6 +39,8 @@ import sop.operation.Decrypt;
public class DecryptImpl implements Decrypt {
private final ConsumerOptions consumerOptions = new ConsumerOptions();
private final Set<PGPSecretKeyRing> keys = new HashSet<>();
private final MatchMakingSecretKeyRingProtector protector = new MatchMakingSecretKeyRingProtector();
@Override
public DecryptImpl verifyNotBefore(Date timestamp) throws SOPGPException.UnsupportedOption {
@ -96,29 +100,34 @@ public class DecryptImpl implements Decrypt {
}
@Override
public DecryptImpl withKey(InputStream keyIn) throws SOPGPException.KeyIsProtected, SOPGPException.BadData, SOPGPException.UnsupportedAsymmetricAlgo {
public DecryptImpl withKey(InputStream keyIn) throws SOPGPException.BadData, SOPGPException.UnsupportedAsymmetricAlgo {
try {
PGPSecretKeyRingCollection secretKeys = PGPainless.readKeyRing()
PGPSecretKeyRingCollection secretKeyCollection = PGPainless.readKeyRing()
.secretKeyRingCollection(keyIn);
for (PGPSecretKeyRing secretKey : secretKeys) {
KeyRingInfo info = new KeyRingInfo(secretKey);
if (!info.isFullyDecrypted()) {
throw new SOPGPException.KeyIsProtected();
}
for (PGPSecretKeyRing key : secretKeyCollection) {
keys.add(key);
}
consumerOptions.addDecryptionKeys(secretKeys, SecretKeyRingProtector.unprotectedKeys());
} catch (IOException | PGPException e) {
throw new SOPGPException.BadData(e);
}
return this;
}
@Override
public Decrypt withKeyPassword(byte[] password) {
String string = new String(password, Charset.forName("UTF8"));
protector.addPassphrase(Passphrase.fromPassword(string));
return this;
}
@Override
public ReadyWithResult<DecryptionResult> ciphertext(InputStream ciphertext)
throws SOPGPException.BadData,
SOPGPException.MissingArg {
for (PGPSecretKeyRing key : keys) {
protector.addSecretKey(key);
consumerOptions.addDecryptionKey(key, protector);
}
if (consumerOptions.getDecryptionKeys().isEmpty() && consumerOptions.getDecryptionPassphrases().isEmpty() && consumerOptions.getSessionKey() == null) {
throw new SOPGPException.MissingArg("Missing decryption key, passphrase or session key.");
@ -131,8 +140,12 @@ public class DecryptImpl implements Decrypt {
.withOptions(consumerOptions);
} catch (MissingDecryptionMethodException e) {
throw new SOPGPException.CannotDecrypt();
} catch (WrongPassphraseException e) {
throw new SOPGPException.KeyIsProtected();
} catch (PGPException | IOException e) {
throw new SOPGPException.BadData(e);
} finally {
protector.clear();
}
return new ReadyWithResult<DecryptionResult>() {

View file

@ -32,28 +32,28 @@ import sop.ReadyWithResult;
import sop.SigningResult;
import sop.enums.SignAs;
import sop.exception.SOPGPException;
import sop.operation.Sign;
import sop.operation.DetachedSign;
public class SignImpl implements Sign {
public class DetachedSignImpl implements DetachedSign {
private boolean armor = true;
private SignAs mode = SignAs.Binary;
private final SigningOptions signingOptions = new SigningOptions();
@Override
public Sign noArmor() {
public DetachedSign noArmor() {
armor = false;
return this;
}
@Override
public Sign mode(SignAs mode) {
public DetachedSign mode(SignAs mode) {
this.mode = mode;
return this;
}
@Override
public Sign key(InputStream keyIn) throws SOPGPException.KeyIsProtected, SOPGPException.BadData, IOException {
public DetachedSign key(InputStream keyIn) throws SOPGPException.KeyIsProtected, SOPGPException.BadData, IOException {
try {
PGPSecretKeyRingCollection keys = PGPainless.readKeyRing().secretKeyRingCollection(keyIn);
@ -70,6 +70,11 @@ public class SignImpl implements Sign {
return this;
}
@Override
public DetachedSign withKeyPassword(byte[] password) {
return null;
}
@Override
public ReadyWithResult<SigningResult> data(InputStream data) throws IOException {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();

View file

@ -21,26 +21,26 @@ import org.pgpainless.decryption_verification.OpenPgpMetadata;
import org.pgpainless.key.SubkeyIdentifier;
import sop.Verification;
import sop.exception.SOPGPException;
import sop.operation.Verify;
import sop.operation.DetachedVerify;
public class VerifyImpl implements Verify {
public class DetachedVerifyImpl implements DetachedVerify {
private final ConsumerOptions options = new ConsumerOptions();
@Override
public Verify notBefore(Date timestamp) throws SOPGPException.UnsupportedOption {
public DetachedVerify notBefore(Date timestamp) throws SOPGPException.UnsupportedOption {
options.verifyNotBefore(timestamp);
return this;
}
@Override
public Verify notAfter(Date timestamp) throws SOPGPException.UnsupportedOption {
public DetachedVerify notAfter(Date timestamp) throws SOPGPException.UnsupportedOption {
options.verifyNotAfter(timestamp);
return this;
}
@Override
public Verify cert(InputStream cert) throws SOPGPException.BadData {
public DetachedVerify cert(InputStream cert) throws SOPGPException.BadData {
PGPPublicKeyRingCollection certificates;
try {
certificates = PGPainless.readKeyRing().publicKeyRingCollection(cert);
@ -52,7 +52,7 @@ public class VerifyImpl implements Verify {
}
@Override
public VerifyImpl signatures(InputStream signatures) throws SOPGPException.BadData {
public DetachedVerifyImpl signatures(InputStream signatures) throws SOPGPException.BadData {
try {
options.addVerificationOfDetachedSignatures(signatures);
} catch (IOException | PGPException e) {

View file

@ -7,9 +7,13 @@ package org.pgpainless.sop;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.HashSet;
import java.util.Set;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
import org.bouncycastle.util.io.Streams;
import org.pgpainless.PGPainless;
@ -20,7 +24,6 @@ import org.pgpainless.encryption_signing.EncryptionStream;
import org.pgpainless.encryption_signing.ProducerOptions;
import org.pgpainless.encryption_signing.SigningOptions;
import org.pgpainless.exception.WrongPassphraseException;
import org.pgpainless.key.protection.SecretKeyRingProtector;
import org.pgpainless.util.Passphrase;
import sop.util.ProxyOutputStream;
import sop.Ready;
@ -33,6 +36,9 @@ public class EncryptImpl implements Encrypt {
EncryptionOptions encryptionOptions = new EncryptionOptions();
SigningOptions signingOptions = null;
Set<PGPSecretKeyRing> signingKeys = new HashSet<>();
MatchMakingSecretKeyRingProtector protector = new MatchMakingSecretKeyRingProtector();
private EncryptAs encryptAs = EncryptAs.Binary;
boolean armor = true;
@ -49,7 +55,7 @@ public class EncryptImpl implements Encrypt {
}
@Override
public Encrypt signWith(InputStream keyIn) throws SOPGPException.KeyIsProtected, SOPGPException.KeyCannotSign, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.BadData {
public Encrypt signWith(InputStream keyIn) throws SOPGPException.KeyCannotSign, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.BadData {
try {
PGPSecretKeyRingCollection keys = PGPainless.readKeyRing().secretKeyRingCollection(keyIn);
if (keys.size() != 1) {
@ -59,23 +65,20 @@ public class EncryptImpl implements Encrypt {
if (signingOptions == null) {
signingOptions = SigningOptions.get();
}
try {
signingOptions.addInlineSignatures(
SecretKeyRingProtector.unprotectedKeys(),
keys,
(encryptAs == EncryptAs.Binary ? DocumentSignatureType.BINARY_DOCUMENT : DocumentSignatureType.CANONICAL_TEXT_DOCUMENT)
);
} catch (IllegalArgumentException e) {
throw new SOPGPException.KeyCannotSign();
} catch (WrongPassphraseException e) {
throw new SOPGPException.KeyIsProtected();
}
signingKeys.add(keys.getKeyRings().next());
} catch (IOException | PGPException e) {
throw new SOPGPException.BadData(e);
}
return this;
}
@Override
public Encrypt withKeyPassword(byte[] password) {
String passphrase = new String(password, Charset.forName("UTF8"));
protector.addPassphrase(Passphrase.fromPassword(passphrase));
return this;
}
@Override
public Encrypt withPassword(String password) throws SOPGPException.PasswordNotHumanReadable, SOPGPException.UnsupportedOption {
encryptionOptions.addPassphrase(Passphrase.fromPassword(password));
@ -97,6 +100,26 @@ public class EncryptImpl implements Encrypt {
@Override
public Ready plaintext(InputStream plaintext) throws IOException {
for (PGPSecretKeyRing signingKey : signingKeys) {
protector.addSecretKey(signingKey);
}
if (signingOptions != null) {
try {
signingOptions.addInlineSignatures(
protector,
signingKeys,
(encryptAs == EncryptAs.Binary ? DocumentSignatureType.BINARY_DOCUMENT : DocumentSignatureType.CANONICAL_TEXT_DOCUMENT)
);
} catch (IllegalArgumentException e) {
throw new SOPGPException.KeyCannotSign();
} catch (WrongPassphraseException e) {
throw new SOPGPException.KeyIsProtected();
} catch (PGPException e) {
throw new SOPGPException.BadData(e);
}
}
ProducerOptions producerOptions = signingOptions != null ?
ProducerOptions.signAndEncrypt(encryptionOptions, signingOptions) :
ProducerOptions.encrypt(encryptionOptions);
@ -125,7 +148,6 @@ public class EncryptImpl implements Encrypt {
private static StreamEncoding encryptAsToStreamEncoding(EncryptAs encryptAs) {
switch (encryptAs) {
case Binary:
case MIME:
return StreamEncoding.BINARY;
case Text:
return StreamEncoding.UTF8;

View file

@ -19,6 +19,7 @@ import org.pgpainless.PGPainless;
import org.pgpainless.key.modification.secretkeyring.SecretKeyRingEditorInterface;
import org.pgpainless.key.protection.SecretKeyRingProtector;
import org.pgpainless.util.ArmorUtils;
import org.pgpainless.util.Passphrase;
import sop.Ready;
import sop.exception.SOPGPException;
import sop.operation.GenerateKey;
@ -27,6 +28,7 @@ public class GenerateKeyImpl implements GenerateKey {
private boolean armor = true;
private final Set<String> userIds = new LinkedHashSet<>();
private Passphrase passphrase;
@Override
public GenerateKey noArmor() {
@ -40,6 +42,12 @@ public class GenerateKeyImpl implements GenerateKey {
return this;
}
@Override
public GenerateKey withKeyPassword(String password) {
this.passphrase = Passphrase.fromPassword(password);
return this;
}
@Override
public Ready generate() throws SOPGPException.MissingArg, SOPGPException.UnsupportedAsymmetricAlgo {
Iterator<String> userIdIterator = userIds.iterator();
@ -50,7 +58,7 @@ public class GenerateKeyImpl implements GenerateKey {
PGPSecretKeyRing key;
try {
key = PGPainless.generateKeyRing()
.modernKeyRing(userIdIterator.next(), null);
.modernKeyRing(userIdIterator.next(), passphrase);
if (userIdIterator.hasNext()) {
SecretKeyRingEditorInterface editor = PGPainless.modifyKeyRing(key);

View file

@ -20,14 +20,14 @@ import org.pgpainless.util.ArmoredOutputStreamFactory;
import sop.ReadyWithResult;
import sop.Signatures;
import sop.exception.SOPGPException;
import sop.operation.DetachInbandSignatureAndMessage;
import sop.operation.InlineDetach;
public class DetachInbandSignatureAndMessageImpl implements DetachInbandSignatureAndMessage {
public class InlineDetachImpl implements InlineDetach {
private boolean armor = true;
@Override
public DetachInbandSignatureAndMessage noArmor() {
public InlineDetach noArmor() {
this.armor = false;
return this;
}

View file

@ -0,0 +1,42 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.sop;
import sop.ReadyWithResult;
import sop.SigningResult;
import sop.enums.InlineSignAs;
import sop.exception.SOPGPException;
import sop.operation.DetachedSign;
import sop.operation.InlineSign;
import java.io.IOException;
import java.io.InputStream;
public class InlineSignImpl implements InlineSign {
@Override
public DetachedSign mode(InlineSignAs mode) throws SOPGPException.UnsupportedOption {
return null;
}
@Override
public DetachedSign noArmor() {
return null;
}
@Override
public InlineSign key(InputStream key) throws SOPGPException.KeyIsProtected, SOPGPException.BadData, IOException {
return null;
}
@Override
public InlineSign withKeyPassword(byte[] password) {
return null;
}
@Override
public ReadyWithResult<SigningResult> data(InputStream data) throws IOException, SOPGPException.ExpectedText {
return null;
}
}

View file

@ -0,0 +1,37 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.sop;
import sop.ReadyWithResult;
import sop.Verification;
import sop.exception.SOPGPException;
import sop.operation.InlineVerify;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.List;
public class InlineVerifyImpl implements InlineVerify {
@Override
public ReadyWithResult<List<Verification>> data(InputStream data) throws IOException, SOPGPException.NoSignature, SOPGPException.BadData {
return null;
}
@Override
public InlineVerify notBefore(Date timestamp) throws SOPGPException.UnsupportedOption {
return null;
}
@Override
public InlineVerify notAfter(Date timestamp) throws SOPGPException.UnsupportedOption {
return null;
}
@Override
public InlineVerify cert(InputStream cert) throws SOPGPException.BadData {
return null;
}
}

View file

@ -0,0 +1,101 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.sop;
import java.util.HashSet;
import java.util.Set;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor;
import org.jetbrains.annotations.Nullable;
import org.pgpainless.implementation.ImplementationFactory;
import org.pgpainless.key.info.KeyInfo;
import org.pgpainless.key.protection.CachingSecretKeyRingProtector;
import org.pgpainless.key.protection.SecretKeyRingProtector;
import org.pgpainless.key.protection.UnlockSecretKey;
import org.pgpainless.util.Passphrase;
public class MatchMakingSecretKeyRingProtector implements SecretKeyRingProtector {
private final Set<Passphrase> passphrases = new HashSet<>();
private final Set<PGPSecretKeyRing> keys = new HashSet<>();
private final CachingSecretKeyRingProtector protector = new CachingSecretKeyRingProtector();
public void addPassphrase(Passphrase passphrase) {
if (passphrase.isEmpty()) {
return;
}
if (!passphrases.add(passphrase)) {
return;
}
for (PGPSecretKeyRing key : keys) {
for (PGPSecretKey subkey : key) {
if (protector.hasPassphrase(subkey.getKeyID())) {
continue;
}
testPassphrase(passphrase, subkey);
}
}
}
public void addSecretKey(PGPSecretKeyRing key) {
if (!keys.add(key)) {
return;
}
for (PGPSecretKey subkey : key) {
if (KeyInfo.isDecrypted(subkey)) {
protector.addPassphrase(subkey.getKeyID(), Passphrase.emptyPassphrase());
} else {
for (Passphrase passphrase : passphrases) {
testPassphrase(passphrase, subkey);
}
}
}
}
private void testPassphrase(Passphrase passphrase, PGPSecretKey subkey) {
try {
PBESecretKeyDecryptor decryptor = ImplementationFactory.getInstance().getPBESecretKeyDecryptor(passphrase);
UnlockSecretKey.unlockSecretKey(subkey, decryptor);
protector.addPassphrase(subkey.getKeyID(), passphrase);
} catch (PGPException e) {
// wrong password
}
}
@Override
public boolean hasPassphraseFor(Long keyId) {
return protector.hasPassphrase(keyId);
}
@Nullable
@Override
public PBESecretKeyDecryptor getDecryptor(Long keyId) throws PGPException {
return protector.getDecryptor(keyId);
}
@Nullable
@Override
public PBESecretKeyEncryptor getEncryptor(Long keyId) throws PGPException {
return protector.getEncryptor(keyId);
}
public void clear() {
for (Passphrase passphrase : passphrases) {
passphrase.clear();
}
for (PGPSecretKeyRing key : keys) {
protector.forgetPassphrase(key);
}
}
}

View file

@ -8,12 +8,14 @@ import sop.SOP;
import sop.operation.Armor;
import sop.operation.Dearmor;
import sop.operation.Decrypt;
import sop.operation.DetachInbandSignatureAndMessage;
import sop.operation.DetachedSign;
import sop.operation.DetachedVerify;
import sop.operation.InlineDetach;
import sop.operation.Encrypt;
import sop.operation.ExtractCert;
import sop.operation.GenerateKey;
import sop.operation.Sign;
import sop.operation.Verify;
import sop.operation.InlineSign;
import sop.operation.InlineVerify;
import sop.operation.Version;
public class SOPImpl implements SOP {
@ -34,13 +36,33 @@ public class SOPImpl implements SOP {
}
@Override
public Sign sign() {
return new SignImpl();
public DetachedSign sign() {
return detachedSign();
}
@Override
public Verify verify() {
return new VerifyImpl();
public DetachedSign detachedSign() {
return new DetachedSignImpl();
}
@Override
public InlineSign inlineSign() {
return new InlineSignImpl();
}
@Override
public DetachedVerify verify() {
return detachedVerify();
}
@Override
public DetachedVerify detachedVerify() {
return new DetachedVerifyImpl();
}
@Override
public InlineVerify inlineVerify() {
return new InlineVerifyImpl();
}
@Override
@ -64,7 +86,7 @@ public class SOPImpl implements SOP {
}
@Override
public DetachInbandSignatureAndMessage detachInbandSignatureAndMessage() {
return new DetachInbandSignatureAndMessageImpl();
public InlineDetach inlineDetach() {
return new InlineDetachImpl();
}
}