Compare commits

...

8 Commits

12 changed files with 119 additions and 14 deletions

View File

@ -14,9 +14,12 @@ SPDX-License-Identifier: CC0-1.0
- Rewrote most of the codebase in Kotlin - Rewrote most of the codebase in Kotlin
- Removed `OpenPgpMetadata` (`decryptionStream.getResult()`) in favor of `MessageMetadata` (`decryptionStream.getMetadata()`) - Removed `OpenPgpMetadata` (`decryptionStream.getResult()`) in favor of `MessageMetadata` (`decryptionStream.getMetadata()`)
- `pgpainless-sop`, `pgpainless-cli` - `pgpainless-sop`, `pgpainless-cli`
- Bump `sop-java` to `8.0.1`, implementing [SOP Spec Revision 08](https://www.ietf.org/archive/id/draft-dkg-openpgp-stateless-cli-08.html) - Bump `sop-java` to `10.0.0`, implementing [SOP Spec Revision 10](https://www.ietf.org/archive/id/draft-dkg-openpgp-stateless-cli-10.html)
- Change API of `sop.encrypt` to return a `ReadyWithResult<EncryptionResult>` to expose the session key - Change API of `sop.encrypt` to return a `ReadyWithResult<EncryptionResult>` to expose the session key
- `decrypt --verify-with`: Fix to not throw `NoSignature` exception (exit code 3) if `VERIFICATIONS` is empty - `decrypt --verify-with`: Fix to not throw `NoSignature` exception (exit code 3) if `VERIFICATIONS` is empty
- Separate signature verification operations into `SOPV` interface
- Add `version --sopv` option
- Throw `BadData` error when passing KEYS where CERTS are expected.
- Properly feed EOS tokens to the pushdown automaton when reaching the end of stream (thanks @iNPUTmice) - Properly feed EOS tokens to the pushdown automaton when reaching the end of stream (thanks @iNPUTmice)
- Do not choke on unknown signature subpackets (thanks @Jerbell) - Do not choke on unknown signature subpackets (thanks @Jerbell)
- Prevent timing issues resuting in subkey binding signatures predating the subkey (@thanks Jerbell) - Prevent timing issues resuting in subkey binding signatures predating the subkey (@thanks Jerbell)
@ -24,6 +27,12 @@ SPDX-License-Identifier: CC0-1.0
- `GNUPG_AEAD_ENCRYPTED_DATA` -> `LIBREPGP_OCB_ENCRYPTED_DATA` - `GNUPG_AEAD_ENCRYPTED_DATA` -> `LIBREPGP_OCB_ENCRYPTED_DATA`
- `GNUPG_VERSION_5_PUBLIC_KEY` -> `LIBREPGP_VERSION_5_PUBLIC_KEY` - `GNUPG_VERSION_5_PUBLIC_KEY` -> `LIBREPGP_VERSION_5_PUBLIC_KEY`
## 1.6.7
- SOP: Fix OOM error when detached-signing large amounts of data (fix #432)
- Move `CachingBcPublicKeyDataDecryptorFactory` from `org.bouncycastle` packet to `org.pgpainless.decryption_verification` to avoid package split (partially addresses #428)
- Basic support for Java Modules for `pgpainless-core` and `pgpainless-sop`
- Added `Automatic-Module-Name` directive to gradle build files
## 1.6.6 ## 1.6.6
- Downgrade `logback-core` and `logback-classic` to `1.2.13` to fix #426 - Downgrade `logback-core` and `logback-classic` to `1.2.13` to fix #426

View File

@ -27,3 +27,10 @@ dependencies {
// @Nullable, @Nonnull annotations // @Nullable, @Nonnull annotations
implementation "com.google.code.findbugs:jsr305:3.0.2" implementation "com.google.code.findbugs:jsr305:3.0.2"
} }
// https://docs.gradle.org/current/userguide/java_library_plugin.html#sec:java_library_modular_auto
tasks.named('jar') {
manifest {
attributes('Automatic-Module-Name': 'org.pgpainless.core')
}
}

View File

@ -6,7 +6,7 @@ SPDX-License-Identifier: Apache-2.0
# PGPainless-SOP # PGPainless-SOP
[![Spec Revision: 8](https://img.shields.io/badge/Spec%20Revision-8-blue)](https://datatracker.ietf.org/doc/draft-dkg-openpgp-stateless-cli/) [![Spec Revision: 10](https://img.shields.io/badge/Spec%20Revision-10-blue)](https://datatracker.ietf.org/doc/draft-dkg-openpgp-stateless-cli/)
[![Maven Central](https://badgen.net/maven/v/maven-central/org.pgpainless/pgpainless-sop)](https://search.maven.org/artifact/org.pgpainless/pgpainless-sop) [![Maven Central](https://badgen.net/maven/v/maven-central/org.pgpainless/pgpainless-sop)](https://search.maven.org/artifact/org.pgpainless/pgpainless-sop)
[![javadoc](https://javadoc.io/badge2/org.pgpainless/pgpainless-sop/javadoc.svg)](https://javadoc.io/doc/org.pgpainless/pgpainless-sop) [![javadoc](https://javadoc.io/badge2/org.pgpainless/pgpainless-sop/javadoc.svg)](https://javadoc.io/doc/org.pgpainless/pgpainless-sop)

View File

@ -34,3 +34,10 @@ test {
useJUnitPlatform() useJUnitPlatform()
environment("test.implementation", "sop.testsuite.pgpainless.PGPainlessSopInstanceFactory") environment("test.implementation", "sop.testsuite.pgpainless.PGPainlessSopInstanceFactory")
} }
// https://docs.gradle.org/current/userguide/java_library_plugin.html#sec:java_library_modular_auto
tasks.named('jar') {
manifest {
attributes('Automatic-Module-Name': 'org.pgpainless.sop')
}
}

View File

@ -4,7 +4,6 @@
package org.pgpainless.sop; package org.pgpainless.sop;
import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
@ -98,10 +97,10 @@ public class DetachedSignImpl implements DetachedSign {
} }
} }
ByteArrayOutputStream buffer = new ByteArrayOutputStream(); OutputStream sink = new NullOutputStream();
try { try {
EncryptionStream signingStream = PGPainless.encryptAndOrSign() EncryptionStream signingStream = PGPainless.encryptAndOrSign()
.onOutputStream(buffer) .onOutputStream(sink)
.withOptions(ProducerOptions.sign(signingOptions) .withOptions(ProducerOptions.sign(signingOptions)
.setAsciiArmor(armor)); .setAsciiArmor(armor));

View File

@ -6,8 +6,10 @@ package org.pgpainless.sop;
import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
import org.bouncycastle.openpgp.PGPRuntimeOperationException;
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection; import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
import org.pgpainless.PGPainless; import org.pgpainless.PGPainless;
import org.pgpainless.key.collection.PGPKeyRingCollection;
import sop.exception.SOPGPException; import sop.exception.SOPGPException;
import java.io.IOException; import java.io.IOException;
@ -44,19 +46,24 @@ class KeyReader {
static PGPPublicKeyRingCollection readPublicKeys(InputStream certIn, boolean requireContent) static PGPPublicKeyRingCollection readPublicKeys(InputStream certIn, boolean requireContent)
throws IOException { throws IOException {
PGPPublicKeyRingCollection certs; PGPKeyRingCollection certs;
try { try {
certs = PGPainless.readKeyRing().publicKeyRingCollection(certIn); certs = PGPainless.readKeyRing().keyRingCollection(certIn, false);
} catch (IOException e) { } catch (IOException e) {
String msg = e.getMessage(); String msg = e.getMessage();
if (msg != null && (msg.startsWith("unknown object in stream:") || msg.startsWith("invalid header encountered"))) { if (msg != null && (msg.startsWith("unknown object in stream:") || msg.startsWith("invalid header encountered"))) {
throw new SOPGPException.BadData(e); throw new SOPGPException.BadData(e);
} }
throw e; throw e;
} catch (PGPRuntimeOperationException e) {
throw new SOPGPException.BadData(e);
} }
if (requireContent && certs.size() == 0) { if (certs.getPgpSecretKeyRingCollection().size() != 0) {
throw new SOPGPException.BadData("Secret key components encountered, while certificates were expected.");
}
if (requireContent && certs.getPgpPublicKeyRingCollection().size() == 0) {
throw new SOPGPException.BadData(new PGPException("No cert data found.")); throw new SOPGPException.BadData(new PGPException("No cert data found."));
} }
return certs; return certs.getPgpPublicKeyRingCollection();
} }
} }

View File

@ -0,0 +1,17 @@
// SPDX-FileCopyrightText: 2024 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.sop;
import java.io.OutputStream;
/**
* {@link OutputStream} that simply discards bytes written to it.
*/
public class NullOutputStream extends OutputStream {
@Override
public void write(int b) {
// NOP
}
}

View File

@ -6,6 +6,7 @@ package org.pgpainless.sop;
import org.pgpainless.util.ArmoredOutputStreamFactory; import org.pgpainless.util.ArmoredOutputStreamFactory;
import sop.SOP; import sop.SOP;
import sop.SOPV;
import sop.operation.Armor; import sop.operation.Armor;
import sop.operation.ChangeKeyPassword; import sop.operation.ChangeKeyPassword;
import sop.operation.Dearmor; import sop.operation.Dearmor;
@ -36,10 +37,12 @@ public class SOPImpl implements SOP {
ArmoredOutputStreamFactory.setVersionInfo(null); ArmoredOutputStreamFactory.setVersionInfo(null);
} }
private final SOPV sopv = new SOPVImpl();
@Override @Override
@Nonnull @Nonnull
public Version version() { public Version version() {
return new VersionImpl(); return sopv.version();
} }
@Override @Override
@ -81,13 +84,13 @@ public class SOPImpl implements SOP {
@Override @Override
@Nonnull @Nonnull
public DetachedVerify detachedVerify() { public DetachedVerify detachedVerify() {
return new DetachedVerifyImpl(); return sopv.detachedVerify();
} }
@Override @Override
@Nonnull @Nonnull
public InlineVerify inlineVerify() { public InlineVerify inlineVerify() {
return new InlineVerifyImpl(); return sopv.inlineVerify();
} }
@Override @Override

View File

@ -0,0 +1,40 @@
// SPDX-FileCopyrightText: 2024 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.sop;
import org.jetbrains.annotations.NotNull;
import org.pgpainless.util.ArmoredOutputStreamFactory;
import sop.SOPV;
import sop.operation.DetachedVerify;
import sop.operation.InlineVerify;
import sop.operation.Version;
/**
* Implementation of the <pre>sopv</pre> interface subset using PGPainless.
*/
public class SOPVImpl implements SOPV {
static {
ArmoredOutputStreamFactory.setVersionInfo(null);
}
@NotNull
@Override
public DetachedVerify detachedVerify() {
return new DetachedVerifyImpl();
}
@NotNull
@Override
public InlineVerify inlineVerify() {
return new InlineVerifyImpl();
}
@NotNull
@Override
public Version version() {
return new VersionImpl();
}
}

View File

@ -10,6 +10,8 @@ import java.util.Locale;
import java.util.Properties; import java.util.Properties;
import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.jetbrains.annotations.NotNull;
import sop.exception.SOPGPException;
import sop.operation.Version; import sop.operation.Version;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
@ -20,7 +22,9 @@ import javax.annotation.Nonnull;
public class VersionImpl implements Version { public class VersionImpl implements Version {
// draft version // draft version
private static final int SOP_VERSION = 8; private static final int SOP_VERSION = 10;
private static final String SOPV_VERSION = "1.0";
@Override @Override
@Nonnull @Nonnull
@ -86,4 +90,9 @@ public class VersionImpl implements Version {
return null; return null;
} }
@NotNull
@Override
public String getSopVVersion() throws SOPGPException.UnsupportedOption {
return SOPV_VERSION;
}
} }

View File

@ -72,4 +72,11 @@ public class VersionTest {
assertTrue(fullSopSpecVersion.endsWith(incompletenessRemarks)); assertTrue(fullSopSpecVersion.endsWith(incompletenessRemarks));
} }
} }
@Test
public void testGetSopVVersion() {
String sopVVersion = sop.version().getSopVVersion();
assertNotNull(sopVVersion);
assertTrue(sopVVersion.matches("\\d+\\.\\d+(\\.\\d+)*")); // X.Y or X.Y.Z... etc.
}
} }

View File

@ -14,6 +14,6 @@ allprojects {
logbackVersion = '1.2.13' logbackVersion = '1.2.13'
mockitoVersion = '4.5.1' mockitoVersion = '4.5.1'
slf4jVersion = '1.7.36' slf4jVersion = '1.7.36'
sopJavaVersion = '8.0.1' sopJavaVersion = '10.0.0'
} }
} }