mirror of
https://github.com/pgpainless/pgpainless.git
synced 2024-12-22 19:08:00 +01:00
Wip: SOP 4
This commit is contained in:
parent
9cdea63ec4
commit
9a545a2936
16 changed files with 429 additions and 58 deletions
|
@ -200,7 +200,7 @@ public final class KeyRingTemplates {
|
|||
* @throws PGPException in case of an OpenPGP related error
|
||||
*/
|
||||
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)
|
||||
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()
|
||||
.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.EDDSA(EdDSACurve._Ed25519), KeyFlag.SIGN_DATA))
|
||||
.addUserId(userId);
|
||||
if (!isNullOrEmpty(password)) {
|
||||
builder.setPassphrase(Passphrase.fromPassword(password));
|
||||
if (passphrase != null && !passphrase.isEmpty()) {
|
||||
builder.setPassphrase(passphrase);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
|
|
@ -10,9 +10,11 @@ group 'org.pgpainless'
|
|||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
mavenLocal()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'org.jetbrains:annotations:20.1.0'
|
||||
testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion"
|
||||
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
|
||||
|
||||
|
|
|
@ -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>() {
|
||||
|
|
|
@ -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();
|
|
@ -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) {
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ public class ArmorTest {
|
|||
|
||||
@Test
|
||||
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[] armored = new SOPImpl()
|
||||
.armor()
|
||||
|
|
|
@ -58,7 +58,7 @@ public class DetachInbandSignatureAndMessageTest {
|
|||
signingStream.close();
|
||||
|
||||
// actually detach the message
|
||||
ByteArrayAndResult<Signatures> detachedMsg = sop.detachInbandSignatureAndMessage()
|
||||
ByteArrayAndResult<Signatures> detachedMsg = sop.inlineDetach()
|
||||
.message(out.toByteArray())
|
||||
.toByteArrayAndResult();
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
|
|||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.bouncycastle.util.io.Streams;
|
||||
|
@ -24,12 +25,13 @@ import sop.exception.SOPGPException;
|
|||
|
||||
public class EncryptDecryptRoundTripTest {
|
||||
|
||||
private static final Charset utf8 = Charset.forName("UTF8");
|
||||
private static SOP sop;
|
||||
private static byte[] aliceKey;
|
||||
private static byte[] aliceCert;
|
||||
private static byte[] bobKey;
|
||||
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
|
||||
public static void setup() throws IOException {
|
||||
|
@ -218,8 +220,119 @@ public class EncryptDecryptRoundTripTest {
|
|||
"=MUYS\n" +
|
||||
"-----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()
|
||||
.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
|
||||
|
|
|
@ -12,6 +12,6 @@ allprojects {
|
|||
slf4jVersion = '1.7.36'
|
||||
logbackVersion = '1.2.11'
|
||||
junitVersion = '5.8.2'
|
||||
sopJavaVersion = '1.2.3'
|
||||
sopJavaVersion = '1.2.4-SNAPSHOT'
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue