1
0
Fork 0
mirror of https://github.com/pgpainless/pgpainless.git synced 2024-11-22 12:22:06 +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

@ -200,7 +200,7 @@ public final class KeyRingTemplates {
* @throws PGPException in case of an OpenPGP related error * @throws PGPException in case of an OpenPGP related error
*/ */
public PGPSecretKeyRing modernKeyRing(String userId) throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException { public PGPSecretKeyRing modernKeyRing(String userId) throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException {
return modernKeyRing(userId, null); return modernKeyRing(userId, (Passphrase) null);
} }
/** /**
@ -217,13 +217,19 @@ public final class KeyRingTemplates {
*/ */
public PGPSecretKeyRing modernKeyRing(String userId, String password) public PGPSecretKeyRing modernKeyRing(String userId, String password)
throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException { throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException {
Passphrase passphrase = (password != null ? Passphrase.fromPassword(password) : null);
return modernKeyRing(userId, passphrase);
}
public PGPSecretKeyRing modernKeyRing(String userId, Passphrase passphrase)
throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException {
KeyRingBuilder builder = PGPainless.buildKeyRing() KeyRingBuilder builder = PGPainless.buildKeyRing()
.setPrimaryKey(KeySpec.getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519), KeyFlag.CERTIFY_OTHER)) .setPrimaryKey(KeySpec.getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519), KeyFlag.CERTIFY_OTHER))
.addSubkey(KeySpec.getBuilder(KeyType.XDH(XDHSpec._X25519), KeyFlag.ENCRYPT_STORAGE, KeyFlag.ENCRYPT_COMMS)) .addSubkey(KeySpec.getBuilder(KeyType.XDH(XDHSpec._X25519), KeyFlag.ENCRYPT_STORAGE, KeyFlag.ENCRYPT_COMMS))
.addSubkey(KeySpec.getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519), KeyFlag.SIGN_DATA)) .addSubkey(KeySpec.getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519), KeyFlag.SIGN_DATA))
.addUserId(userId); .addUserId(userId);
if (!isNullOrEmpty(password)) { if (passphrase != null && !passphrase.isEmpty()) {
builder.setPassphrase(Passphrase.fromPassword(password)); builder.setPassphrase(passphrase);
} }
return builder.build(); return builder.build();
} }

View file

@ -10,9 +10,11 @@ group 'org.pgpainless'
repositories { repositories {
mavenCentral() mavenCentral()
mavenLocal()
} }
dependencies { dependencies {
implementation 'org.jetbrains:annotations:20.1.0'
testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion" testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion" testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -20,14 +20,14 @@ import org.pgpainless.util.ArmoredOutputStreamFactory;
import sop.ReadyWithResult; import sop.ReadyWithResult;
import sop.Signatures; import sop.Signatures;
import sop.exception.SOPGPException; 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; private boolean armor = true;
@Override @Override
public DetachInbandSignatureAndMessage noArmor() { public InlineDetach noArmor() {
this.armor = false; this.armor = false;
return this; 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.Armor;
import sop.operation.Dearmor; import sop.operation.Dearmor;
import sop.operation.Decrypt; 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.Encrypt;
import sop.operation.ExtractCert; import sop.operation.ExtractCert;
import sop.operation.GenerateKey; import sop.operation.GenerateKey;
import sop.operation.Sign; import sop.operation.InlineSign;
import sop.operation.Verify; import sop.operation.InlineVerify;
import sop.operation.Version; import sop.operation.Version;
public class SOPImpl implements SOP { public class SOPImpl implements SOP {
@ -34,13 +36,33 @@ public class SOPImpl implements SOP {
} }
@Override @Override
public Sign sign() { public DetachedSign sign() {
return new SignImpl(); return detachedSign();
} }
@Override @Override
public Verify verify() { public DetachedSign detachedSign() {
return new VerifyImpl(); 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 @Override
@ -64,7 +86,7 @@ public class SOPImpl implements SOP {
} }
@Override @Override
public DetachInbandSignatureAndMessage detachInbandSignatureAndMessage() { public InlineDetach inlineDetach() {
return new DetachInbandSignatureAndMessageImpl(); return new InlineDetachImpl();
} }
} }

View file

@ -28,7 +28,7 @@ public class ArmorTest {
@Test @Test
public void armor() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException { public void armor() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException {
byte[] data = PGPainless.generateKeyRing().modernKeyRing("Alice", null).getEncoded(); byte[] data = PGPainless.generateKeyRing().modernKeyRing("Alice").getEncoded();
byte[] knownGoodArmor = ArmorUtils.toAsciiArmoredString(data).getBytes(StandardCharsets.UTF_8); byte[] knownGoodArmor = ArmorUtils.toAsciiArmoredString(data).getBytes(StandardCharsets.UTF_8);
byte[] armored = new SOPImpl() byte[] armored = new SOPImpl()
.armor() .armor()

View file

@ -58,7 +58,7 @@ public class DetachInbandSignatureAndMessageTest {
signingStream.close(); signingStream.close();
// actually detach the message // actually detach the message
ByteArrayAndResult<Signatures> detachedMsg = sop.detachInbandSignatureAndMessage() ByteArrayAndResult<Signatures> detachedMsg = sop.inlineDetach()
.message(out.toByteArray()) .message(out.toByteArray())
.toByteArrayAndResult(); .toByteArrayAndResult();

View file

@ -11,6 +11,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import org.bouncycastle.util.io.Streams; import org.bouncycastle.util.io.Streams;
@ -24,12 +25,13 @@ import sop.exception.SOPGPException;
public class EncryptDecryptRoundTripTest { public class EncryptDecryptRoundTripTest {
private static final Charset utf8 = Charset.forName("UTF8");
private static SOP sop; private static SOP sop;
private static byte[] aliceKey; private static byte[] aliceKey;
private static byte[] aliceCert; private static byte[] aliceCert;
private static byte[] bobKey; private static byte[] bobKey;
private static byte[] bobCert; private static byte[] bobCert;
private static byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); private static byte[] message = "Hello, World!\n".getBytes(utf8);
@BeforeAll @BeforeAll
public static void setup() throws IOException { public static void setup() throws IOException {
@ -218,8 +220,119 @@ public class EncryptDecryptRoundTripTest {
"=MUYS\n" + "=MUYS\n" +
"-----END PGP PRIVATE KEY BLOCK-----"; "-----END PGP PRIVATE KEY BLOCK-----";
String msg = "-----BEGIN PGP MESSAGE-----\n" +
"Version: PGPainless\n" +
"\n" +
"hF4Doj0CaB2GRvISAQdAhV5sjUCxanM68jG9qaq2rep1KKQx2o+9yrK0Rsrtqkww\n" +
"mb4uVv/SD3ixDztUSgUset0jeUeZHZAWfTB9cWawX4fiB2BdbcxhxFqQR8VPJ2SZ\n" +
"0jcB+wH1gq05AkMaCfoEIio3o3QcZq2In8tqj69U3AFRQApoH/p+ZLDz2pcnFBn+\n" +
"x1Y+C6wNg/3g\n" +
"=6vge\n" +
"-----END PGP MESSAGE-----";
assertThrows(SOPGPException.KeyIsProtected.class, () -> sop.decrypt() assertThrows(SOPGPException.KeyIsProtected.class, () -> sop.decrypt()
.withKey(passwordProtectedKey.getBytes(StandardCharsets.UTF_8))); .withKey(passwordProtectedKey.getBytes(StandardCharsets.UTF_8))
.ciphertext(msg.getBytes(utf8)));
}
@Test
public void encryptDecryptRoundTripWithProtectedKey() throws IOException {
byte[] passphrase = "sw0rdf1sh".getBytes(utf8);
byte[] key = sop.generateKey()
.userId("Alice <alice@pgpainless.org>")
.withKeyPassword(passphrase)
.generate().getBytes();
byte[] cert = sop.extractCert()
.key(key)
.getBytes();
byte[] plaintext = "Hello, World!\n".getBytes(utf8);
byte[] ciphertext = sop.encrypt()
.withCert(cert)
.plaintext(plaintext)
.getBytes();
byte[] decrypted = sop.decrypt()
.withKeyPassword(passphrase)
.withKey(key)
.ciphertext(ciphertext)
.toByteArrayAndResult()
.getBytes();
assertArrayEquals(plaintext, decrypted);
}
@Test
public void encryptDecryptRoundTripWithTwoProtectedKeysAndOnePassphrase() throws IOException {
byte[] passphrase1 = "sw0rdf1sh".getBytes(utf8);
byte[] key1 = sop.generateKey()
.userId("Alice <alice@pgpainless.org>")
.withKeyPassword(passphrase1)
.generate().getBytes();
byte[] cert1 = sop.extractCert()
.key(key1)
.getBytes();
byte[] passphrase2 = "fooBar".getBytes(utf8);
byte[] key2 = sop.generateKey()
.userId("Bob <bob@pgpainless.org>")
.withKeyPassword(passphrase2)
.generate().getBytes();
byte[] cert2 = sop.extractCert()
.key(key2)
.getBytes();
byte[] plaintext = "Hello, World!\n".getBytes(utf8);
byte[] ciphertext = sop.encrypt()
.withCert(cert1)
.withCert(cert2)
.plaintext(plaintext)
.getBytes();
byte[] decrypted = sop.decrypt()
.withKey(key1)
.withKey(key2)
.withKeyPassword(passphrase2)
.ciphertext(ciphertext)
.toByteArrayAndResult()
.getBytes();
assertArrayEquals(plaintext, decrypted);
}
@Test
public void encryptDecryptRoundTripFailsWithProtectedKeyAndWrongPassphrase() throws IOException {
byte[] passphrase = "sw0rdf1sh".getBytes(utf8);
byte[] key = sop.generateKey()
.userId("Alice <alice@pgpainless.org>")
.withKeyPassword(passphrase)
.generate().getBytes();
byte[] cert = sop.extractCert()
.key(key)
.getBytes();
byte[] plaintext = "Hello, World!\n".getBytes(utf8);
byte[] ciphertext = sop.encrypt()
.withCert(cert)
.plaintext(plaintext)
.getBytes();
assertThrows(SOPGPException.KeyIsProtected.class,
() -> sop.decrypt()
.withKeyPassword("foobar")
.withKey(key)
.ciphertext(ciphertext));
} }
@Test @Test

View file

@ -12,6 +12,6 @@ allprojects {
slf4jVersion = '1.7.36' slf4jVersion = '1.7.36'
logbackVersion = '1.2.11' logbackVersion = '1.2.11'
junitVersion = '5.8.2' junitVersion = '5.8.2'
sopJavaVersion = '1.2.3' sopJavaVersion = '1.2.4-SNAPSHOT'
} }
} }