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
*/
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();
}

View File

@ -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"

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();
}
}

View File

@ -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()

View File

@ -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();

View File

@ -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

View File

@ -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'
}
}