1
0
Fork 0
mirror of https://github.com/pgpainless/pgpainless.git synced 2024-11-22 20:32:05 +01:00

Refactor CleartextSignatureProcessor to allow reuse in DetachInbandSignatureAndMessage

This commit is contained in:
Paul Schaub 2021-08-18 18:27:22 +02:00
parent 40dfa6528a
commit 772f69788b
14 changed files with 498 additions and 203 deletions

View file

@ -0,0 +1,239 @@
/*
* Copyright 2021 Paul Schaub.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.pgpainless.signature.cleartext_signatures;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.bouncycastle.bcpg.ArmoredInputStream;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPObjectFactory;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.openpgp.PGPSignatureGenerator;
import org.bouncycastle.openpgp.PGPSignatureList;
import org.bouncycastle.util.Strings;
import org.pgpainless.implementation.ImplementationFactory;
import org.pgpainless.util.ArmoredInputStreamFactory;
/**
* Utility class to deal with cleartext-signed messages.
* Based on Bouncycastle's {@link org.bouncycastle.openpgp.examples.ClearSignedFileProcessor}.
*/
public final class ClearsignedMessageUtil {
private ClearsignedMessageUtil() {
}
/**
* Dearmor a clearsigned message, detach the inband signatures and write the plaintext message to the provided
* messageOutputStream.
*
* @param clearsignedInputStream input stream containing a clearsigned message
* @param messageOutputStream output stream to which the dearmored message shall be written
* @return signatures
* @throws IOException if the message is not clearsigned or some other IO error happens
*/
public static PGPSignatureList detachSignaturesFromInbandClearsignedMessage(InputStream clearsignedInputStream,
OutputStream messageOutputStream)
throws IOException {
ArmoredInputStream in = ArmoredInputStreamFactory.get(clearsignedInputStream);
if (!in.isClearText()) {
throw new IOException("Message is not clearsigned.");
}
OutputStream out = new BufferedOutputStream(messageOutputStream);
try {
ByteArrayOutputStream lineOut = new ByteArrayOutputStream();
int lookAhead = readInputLine(lineOut, in);
byte[] lineSep = getLineSeparator();
if (lookAhead != -1 && in.isClearText()) {
byte[] line = lineOut.toByteArray();
out.write(line, 0, getLengthWithoutSeparatorOrTrailingWhitespace(line));
out.write(lineSep);
while (lookAhead != -1 && in.isClearText()) {
lookAhead = readInputLine(lineOut, lookAhead, in);
line = lineOut.toByteArray();
out.write(line, 0, getLengthWithoutSeparatorOrTrailingWhitespace(line));
out.write(lineSep);
}
} else {
if (lookAhead != -1) {
byte[] line = lineOut.toByteArray();
out.write(line, 0, getLengthWithoutSeparatorOrTrailingWhitespace(line));
out.write(lineSep);
}
}
} finally {
out.close();
}
PGPObjectFactory objectFactory = new PGPObjectFactory(in, ImplementationFactory.getInstance().getKeyFingerprintCalculator());
PGPSignatureList signatures = (PGPSignatureList) objectFactory.nextObject();
return signatures;
}
/**
* Initialize the given signature by processing the data from the messageData input stream.
*
* @param signature uninitialized signature
* @param signingKey public signing key
* @param messageData input stream containing the data to which the signature belongs
* @return initialized signature
*
* @throws PGPException if the signature cannot be initialized
* @throws IOException if an IO error happens
*/
public static PGPSignature initializeSignature(PGPSignature signature, PGPPublicKey signingKey, InputStream messageData)
throws PGPException, IOException {
signature.init(ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider(), signingKey);
InputStream sigIn = new BufferedInputStream(messageData);
ByteArrayOutputStream lineOut = new ByteArrayOutputStream();
int lookAhead = readInputLine(lineOut, sigIn);
processLine(signature, lineOut.toByteArray());
if (lookAhead != -1) {
do {
lookAhead = readInputLine(lineOut, lookAhead, sigIn);
signature.update((byte) '\r');
signature.update((byte) '\n');
processLine(signature, lineOut.toByteArray());
} while (lookAhead != -1);
}
sigIn.close();
return signature;
}
public static int readInputLine(ByteArrayOutputStream bOut, InputStream fIn)
throws IOException {
bOut.reset();
int lookAhead = -1;
int ch;
while ((ch = fIn.read()) >= 0) {
bOut.write(ch);
if (ch == '\r' || ch == '\n') {
lookAhead = readPassedEOL(bOut, ch, fIn);
break;
}
}
return lookAhead;
}
public static int readInputLine(ByteArrayOutputStream bOut, int lookAhead, InputStream fIn)
throws IOException {
bOut.reset();
int ch = lookAhead;
do {
bOut.write(ch);
if (ch == '\r' || ch == '\n') {
lookAhead = readPassedEOL(bOut, ch, fIn);
break;
}
}
while ((ch = fIn.read()) >= 0);
if (ch < 0) {
lookAhead = -1;
}
return lookAhead;
}
private static int readPassedEOL(ByteArrayOutputStream bOut, int lastCh, InputStream fIn)
throws IOException {
int lookAhead = fIn.read();
if (lastCh == '\r' && lookAhead == '\n') {
bOut.write(lookAhead);
lookAhead = fIn.read();
}
return lookAhead;
}
private static byte[] getLineSeparator() {
String nl = Strings.lineSeparator();
byte[] nlBytes = new byte[nl.length()];
for (int i = 0; i != nlBytes.length; i++) {
nlBytes[i] = (byte) nl.charAt(i);
}
return nlBytes;
}
public static void processLine(PGPSignature sig, byte[] line) {
int length = getLengthWithoutWhiteSpace(line);
if (length > 0) {
sig.update(line, 0, length);
}
}
public static void processLine(OutputStream aOut, PGPSignatureGenerator sGen, byte[] line)
throws IOException {
// note: trailing white space needs to be removed from the end of
// each line for signature calculation RFC 4880 Section 7.1
int length = getLengthWithoutWhiteSpace(line);
if (length > 0) {
sGen.update(line, 0, length);
}
aOut.write(line, 0, line.length);
}
private static int getLengthWithoutSeparatorOrTrailingWhitespace(byte[] line) {
int end = line.length - 1;
while (end >= 0 && isWhiteSpace(line[end])) {
end--;
}
return end + 1;
}
private static boolean isLineEnding(byte b) {
return b == '\r' || b == '\n';
}
private static int getLengthWithoutWhiteSpace(byte[] line) {
int end = line.length - 1;
while (end >= 0 && isWhiteSpace(line[end])) {
end--;
}
return end + 1;
}
private static boolean isWhiteSpace(byte b) {
return isLineEnding(b) || b == '\t' || b == ' ';
}
}

View file

@ -15,36 +15,34 @@
*/ */
package org.pgpainless.signature.cleartext_signatures; package org.pgpainless.signature.cleartext_signatures;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.util.HashMap;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.bouncycastle.bcpg.ArmoredInputStream; import org.bouncycastle.bcpg.ArmoredInputStream;
import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPObjectFactory;
import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing; import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
import org.bouncycastle.openpgp.PGPSignature; import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.openpgp.PGPSignatureGenerator;
import org.bouncycastle.openpgp.PGPSignatureList; import org.bouncycastle.openpgp.PGPSignatureList;
import org.bouncycastle.util.Strings;
import org.pgpainless.PGPainless; import org.pgpainless.PGPainless;
import org.pgpainless.exception.SignatureValidationException; import org.pgpainless.exception.SignatureValidationException;
import org.pgpainless.implementation.ImplementationFactory;
import org.pgpainless.signature.CertificateValidator; import org.pgpainless.signature.CertificateValidator;
import org.pgpainless.util.ArmoredInputStreamFactory; import org.pgpainless.util.ArmoredInputStreamFactory;
/** /**
* Processor for cleartext-signed messages. * Processor for cleartext-signed messages.
* Based on Bouncycastle's {@link org.bouncycastle.openpgp.examples.ClearSignedFileProcessor}.
*/ */
public class CleartextSignatureProcessor { public class CleartextSignatureProcessor {
private static final Logger LOGGER = Logger.getLogger(CleartextSignatureProcessor.class.getName());
private final ArmoredInputStream in; private final ArmoredInputStream in;
private final PGPPublicKeyRingCollection verificationKeys; private final PGPPublicKeyRingCollection verificationKeys;
private final MultiPassStrategy multiPassStrategy; private final MultiPassStrategy multiPassStrategy;
@ -76,182 +74,35 @@ public class CleartextSignatureProcessor {
* @throws SignatureValidationException if the signature is invalid. * @throws SignatureValidationException if the signature is invalid.
*/ */
public PGPSignature process() throws IOException, PGPException { public PGPSignature process() throws IOException, PGPException {
if (!in.isClearText()) { PGPSignatureList signatures = ClearsignedMessageUtil.detachSignaturesFromInbandClearsignedMessage(in, multiPassStrategy.getMessageOutputStream());
throw new IllegalStateException("Message is not cleartext."); Map<PGPSignature, Exception> signatureValidationExceptions = new HashMap<>();
}
OutputStream out = new BufferedOutputStream(multiPassStrategy.getMessageOutputStream()); for (PGPSignature signature : signatures) {
PGPPublicKeyRing certificate = null;
ByteArrayOutputStream lineOut = new ByteArrayOutputStream();
int lookAhead = readInputLine(lineOut, in);
byte[] lineSep = getLineSeparator();
if (lookAhead != -1 && in.isClearText()) {
byte[] line = lineOut.toByteArray();
out.write(line, 0, getLengthWithoutSeparatorOrTrailingWhitespace(line));
out.write(lineSep);
while (lookAhead != -1 && in.isClearText()) {
lookAhead = readInputLine(lineOut, lookAhead, in);
line = lineOut.toByteArray();
out.write(line, 0, getLengthWithoutSeparatorOrTrailingWhitespace(line));
out.write(lineSep);
}
} else {
if (lookAhead != -1) {
byte[] line = lineOut.toByteArray();
out.write(line, 0, getLengthWithoutSeparatorOrTrailingWhitespace(line));
out.write(lineSep);
}
}
out.close();
PGPObjectFactory objectFactory = new PGPObjectFactory(in, ImplementationFactory.getInstance().getKeyFingerprintCalculator());
PGPSignatureList signatures = (PGPSignatureList) objectFactory.nextObject();
PGPSignature signature = signatures.get(0);
PGPPublicKeyRing signingKeyRing = null;
PGPPublicKey signingKey = null; PGPPublicKey signingKey = null;
for (PGPPublicKeyRing ring : verificationKeys) { for (PGPPublicKeyRing cert : verificationKeys) {
signingKey = ring.getPublicKey(signature.getKeyID()); signingKey = cert.getPublicKey(signature.getKeyID());
if (signingKey != null) { if (signingKey != null) {
signingKeyRing = ring; certificate = cert;
break; break;
} }
} }
if (signingKey == null) { if (signingKey == null) {
throw new SignatureValidationException("Missing public key " + Long.toHexString(signature.getKeyID())); signatureValidationExceptions.put(signature, new NoSuchElementException("Missing verification key with key-id " + Long.toHexString(signature.getKeyID())));
continue;
} }
signature.init(ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider(), signingKey); try {
ClearsignedMessageUtil.initializeSignature(signature, signingKey, multiPassStrategy.getMessageInputStream());
InputStream sigIn = new BufferedInputStream(multiPassStrategy.getMessageInputStream()); CertificateValidator.validateCertificateAndVerifyInitializedSignature(signature, certificate, PGPainless.getPolicy());
lookAhead = readInputLine(lineOut, sigIn);
processLine(signature, lineOut.toByteArray());
if (lookAhead != -1) {
do {
lookAhead = readInputLine(lineOut, lookAhead, sigIn);
signature.update((byte) '\r');
signature.update((byte) '\n');
processLine(signature, lineOut.toByteArray());
} while (lookAhead != -1);
}
sigIn.close();
CertificateValidator.validateCertificateAndVerifyInitializedSignature(signature, signingKeyRing, PGPainless.getPolicy());
return signature; return signature;
} } catch (SignatureValidationException e) {
LOGGER.log(Level.INFO, "Cannot verify signature made by key " + Long.toHexString(signature.getKeyID()) + ": " + e.getMessage());
private static int readInputLine(ByteArrayOutputStream bOut, InputStream fIn) signatureValidationExceptions.put(signature, e);
throws IOException {
bOut.reset();
int lookAhead = -1;
int ch;
while ((ch = fIn.read()) >= 0) {
bOut.write(ch);
if (ch == '\r' || ch == '\n') {
lookAhead = readPassedEOL(bOut, ch, fIn);
break;
} }
} }
return lookAhead; throw new SignatureValidationException("No valid signatures found.", signatureValidationExceptions);
} }
private static int readInputLine(ByteArrayOutputStream bOut, int lookAhead, InputStream fIn)
throws IOException {
bOut.reset();
int ch = lookAhead;
do {
bOut.write(ch);
if (ch == '\r' || ch == '\n') {
lookAhead = readPassedEOL(bOut, ch, fIn);
break;
}
}
while ((ch = fIn.read()) >= 0);
if (ch < 0) {
lookAhead = -1;
}
return lookAhead;
}
private static int readPassedEOL(ByteArrayOutputStream bOut, int lastCh, InputStream fIn)
throws IOException {
int lookAhead = fIn.read();
if (lastCh == '\r' && lookAhead == '\n') {
bOut.write(lookAhead);
lookAhead = fIn.read();
}
return lookAhead;
}
private static byte[] getLineSeparator() {
String nl = Strings.lineSeparator();
byte[] nlBytes = new byte[nl.length()];
for (int i = 0; i != nlBytes.length; i++) {
nlBytes[i] = (byte) nl.charAt(i);
}
return nlBytes;
}
private static void processLine(PGPSignature sig, byte[] line) {
int length = getLengthWithoutWhiteSpace(line);
if (length > 0) {
sig.update(line, 0, length);
}
}
private static void processLine(OutputStream aOut, PGPSignatureGenerator sGen, byte[] line)
throws IOException {
// note: trailing white space needs to be removed from the end of
// each line for signature calculation RFC 4880 Section 7.1
int length = getLengthWithoutWhiteSpace(line);
if (length > 0) {
sGen.update(line, 0, length);
}
aOut.write(line, 0, line.length);
}
private static int getLengthWithoutSeparatorOrTrailingWhitespace(byte[] line) {
int end = line.length - 1;
while (end >= 0 && isWhiteSpace(line[end])) {
end--;
}
return end + 1;
}
private static boolean isLineEnding(byte b) {
return b == '\r' || b == '\n';
}
private static int getLengthWithoutWhiteSpace(byte[] line) {
int end = line.length - 1;
while (end >= 0 && isWhiteSpace(line[end])) {
end--;
}
return end + 1;
}
private static boolean isWhiteSpace(byte b) {
return isLineEnding(b) || b == '\t' || b == ' ';
}
} }

View file

@ -47,6 +47,9 @@ public interface MultiPassStrategy {
* Provide an {@link InputStream} which contains the data that was previously written away in * Provide an {@link InputStream} which contains the data that was previously written away in
* {@link #getMessageOutputStream()}. * {@link #getMessageOutputStream()}.
* *
* As there may be multiple signatures that need to be processed, each call of this method MUST return
* a new {@link InputStream}.
*
* @return input stream * @return input stream
* @throws IOException io error * @throws IOException io error
*/ */

View file

@ -32,7 +32,6 @@ import org.pgpainless.PGPainless;
import org.pgpainless.decryption_verification.ConsumerOptions; 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.NotYetImplementedException;
import org.pgpainless.key.SubkeyIdentifier; import org.pgpainless.key.SubkeyIdentifier;
import org.pgpainless.key.info.KeyRingInfo; import org.pgpainless.key.info.KeyRingInfo;
import org.pgpainless.key.protection.SecretKeyRingProtector; import org.pgpainless.key.protection.SecretKeyRingProtector;
@ -50,21 +49,13 @@ public class DecryptImpl implements Decrypt {
@Override @Override
public DecryptImpl verifyNotBefore(Date timestamp) throws SOPGPException.UnsupportedOption { public DecryptImpl verifyNotBefore(Date timestamp) throws SOPGPException.UnsupportedOption {
try {
consumerOptions.verifyNotBefore(timestamp); consumerOptions.verifyNotBefore(timestamp);
} catch (NotYetImplementedException e) {
throw new SOPGPException.UnsupportedOption();
}
return this; return this;
} }
@Override @Override
public DecryptImpl verifyNotAfter(Date timestamp) throws SOPGPException.UnsupportedOption { public DecryptImpl verifyNotAfter(Date timestamp) throws SOPGPException.UnsupportedOption {
try {
consumerOptions.verifyNotAfter(timestamp); consumerOptions.verifyNotAfter(timestamp);
} catch (NotYetImplementedException e) {
throw new SOPGPException.UnsupportedOption();
}
return this; return this;
} }

View file

@ -0,0 +1,69 @@
/*
* Copyright 2021 Paul Schaub.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.pgpainless.sop;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.openpgp.PGPSignatureList;
import org.pgpainless.signature.cleartext_signatures.ClearsignedMessageUtil;
import org.pgpainless.util.ArmoredOutputStreamFactory;
import sop.ReadyWithResult;
import sop.Signatures;
import sop.exception.SOPGPException;
import sop.operation.DetachInbandSignatureAndMessage;
public class DetachInbandSignatureAndMessageImpl implements DetachInbandSignatureAndMessage {
private boolean armor = true;
@Override
public DetachInbandSignatureAndMessage noArmor() {
this.armor = false;
return this;
}
@Override
public ReadyWithResult<Signatures> message(InputStream messageInputStream) {
return new ReadyWithResult<Signatures>() {
@Override
public Signatures writeTo(OutputStream messageOutputStream) throws SOPGPException.NoSignature {
return new Signatures() {
@Override
public void writeTo(OutputStream signatureOutputStream) throws IOException {
PGPSignatureList signatures = ClearsignedMessageUtil.detachSignaturesFromInbandClearsignedMessage(messageInputStream, messageOutputStream);
if (armor) {
ArmoredOutputStream armorOut = ArmoredOutputStreamFactory.get(signatureOutputStream);
for (PGPSignature signature : signatures) {
signature.encode(armorOut);
}
armorOut.close();
} else {
for (PGPSignature signature : signatures) {
signature.encode(signatureOutputStream);
}
}
}
};
}
};
}
}

View file

@ -19,6 +19,7 @@ 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.Encrypt; import sop.operation.Encrypt;
import sop.operation.ExtractCert; import sop.operation.ExtractCert;
import sop.operation.GenerateKey; import sop.operation.GenerateKey;
@ -72,4 +73,9 @@ public class SOPImpl implements SOP {
public Dearmor dearmor() { public Dearmor dearmor() {
return new DearmorImpl(); return new DearmorImpl();
} }
@Override
public DetachInbandSignatureAndMessage detachInbandSignatureAndMessage() {
return new DetachInbandSignatureAndMessageImpl();
}
} }

View file

@ -29,7 +29,6 @@ import org.pgpainless.PGPainless;
import org.pgpainless.decryption_verification.ConsumerOptions; 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.NotYetImplementedException;
import org.pgpainless.key.SubkeyIdentifier; import org.pgpainless.key.SubkeyIdentifier;
import sop.Verification; import sop.Verification;
import sop.exception.SOPGPException; import sop.exception.SOPGPException;
@ -41,21 +40,13 @@ public class VerifyImpl implements Verify {
@Override @Override
public Verify notBefore(Date timestamp) throws SOPGPException.UnsupportedOption { public Verify notBefore(Date timestamp) throws SOPGPException.UnsupportedOption {
try {
options.verifyNotBefore(timestamp); options.verifyNotBefore(timestamp);
} catch (NotYetImplementedException e) {
throw new SOPGPException.UnsupportedOption();
}
return this; return this;
} }
@Override @Override
public Verify notAfter(Date timestamp) throws SOPGPException.UnsupportedOption { public Verify notAfter(Date timestamp) throws SOPGPException.UnsupportedOption {
try {
options.verifyNotAfter(timestamp); options.verifyNotAfter(timestamp);
} catch (NotYetImplementedException e) {
throw new SOPGPException.UnsupportedOption();
}
return this; return this;
} }

View file

@ -20,6 +20,7 @@ import sop.SOP;
import sop.cli.picocli.commands.ArmorCmd; import sop.cli.picocli.commands.ArmorCmd;
import sop.cli.picocli.commands.DearmorCmd; import sop.cli.picocli.commands.DearmorCmd;
import sop.cli.picocli.commands.DecryptCmd; import sop.cli.picocli.commands.DecryptCmd;
import sop.cli.picocli.commands.DetachInbandSignatureAndMessageCmd;
import sop.cli.picocli.commands.EncryptCmd; import sop.cli.picocli.commands.EncryptCmd;
import sop.cli.picocli.commands.ExtractCertCmd; import sop.cli.picocli.commands.ExtractCertCmd;
import sop.cli.picocli.commands.GenerateKeyCmd; import sop.cli.picocli.commands.GenerateKeyCmd;
@ -34,6 +35,7 @@ import sop.cli.picocli.commands.VersionCmd;
ArmorCmd.class, ArmorCmd.class,
DearmorCmd.class, DearmorCmd.class,
DecryptCmd.class, DecryptCmd.class,
DetachInbandSignatureAndMessageCmd.class,
EncryptCmd.class, EncryptCmd.class,
ExtractCertCmd.class, ExtractCertCmd.class,
GenerateKeyCmd.class, GenerateKeyCmd.class,

View file

@ -1,11 +1,66 @@
/*
* Copyright 2021 Paul Schaub.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sop.cli.picocli.commands; package sop.cli.picocli.commands;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import picocli.CommandLine; import picocli.CommandLine;
import sop.Signatures;
import sop.cli.picocli.SopCLI;
import sop.exception.SOPGPException; import sop.exception.SOPGPException;
import sop.operation.DetachInbandSignatureAndMessage;
@CommandLine.Command(name = "detach-inband-signature-and-message", @CommandLine.Command(name = "detach-inband-signature-and-message",
description = "Split a clearsigned message", description = "Split a clearsigned message",
exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE) exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE)
public class DetachInbandSignatureAndMessageCmd implements Runnable { public class DetachInbandSignatureAndMessageCmd implements Runnable {
@CommandLine.Option(
names = {"--signatures-out"},
description = "Destination to which a detached signatures block will be written",
paramLabel = "SIGNATURES")
File signaturesOut;
@CommandLine.Option(names = "--no-armor",
description = "ASCII armor the output",
negatable = true)
boolean armor = true;
@Override
public void run() {
if (signaturesOut == null) {
throw new SOPGPException.MissingArg("--signatures-out is required.");
}
DetachInbandSignatureAndMessage detach = SopCLI.getSop().detachInbandSignatureAndMessage();
if (!armor) {
detach.noArmor();
}
try {
Signatures signatures = detach
.message(System.in).writeTo(System.out);
if (!signaturesOut.createNewFile()) {
throw new SOPGPException.OutputExists("Destination of --signatures-out already exists.");
}
signatures.writeTo(new FileOutputStream(signaturesOut));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
} }

View file

@ -21,8 +21,21 @@ import java.io.OutputStream;
public abstract class Ready { public abstract class Ready {
/**
* Write the data to the provided output stream.
*
* @param outputStream output stream
* @throws IOException in case of an IO error
*/
public abstract void writeTo(OutputStream outputStream) throws IOException; public abstract void writeTo(OutputStream outputStream) throws IOException;
/**
* Return the data as a byte array by writing it to a {@link ByteArrayOutputStream} first and then returning
* the array.
*
* @return data as byte array
* @throws IOException in case of an IO error
*/
public byte[] getBytes() throws IOException { public byte[] getBytes() throws IOException {
ByteArrayOutputStream bytes = new ByteArrayOutputStream(); ByteArrayOutputStream bytes = new ByteArrayOutputStream();
writeTo(bytes); writeTo(bytes);

View file

@ -23,6 +23,16 @@ import sop.exception.SOPGPException;
public abstract class ReadyWithResult<T> { public abstract class ReadyWithResult<T> {
/**
* Write the data eg. decrypted plaintext to the provided output stream and return the result of the
* processing operation.
*
* @param outputStream output stream
* @return result, eg. signatures
*
* @throws IOException in case of an IO error
* @throws SOPGPException.NoSignature
*/
public abstract T writeTo(OutputStream outputStream) throws IOException, SOPGPException.NoSignature; public abstract T writeTo(OutputStream outputStream) throws IOException, SOPGPException.NoSignature;
public ByteArrayAndResult<T> toBytes() throws IOException, SOPGPException.NoSignature { public ByteArrayAndResult<T> toBytes() throws IOException, SOPGPException.NoSignature {

View file

@ -18,6 +18,7 @@ package 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.Encrypt; import sop.operation.Encrypt;
import sop.operation.ExtractCert; import sop.operation.ExtractCert;
import sop.operation.GenerateKey; import sop.operation.GenerateKey;
@ -100,4 +101,6 @@ public interface SOP {
* @return builder instance * @return builder instance
*/ */
Dearmor dearmor(); Dearmor dearmor();
DetachInbandSignatureAndMessage detachInbandSignatureAndMessage();
} }

View file

@ -0,0 +1,32 @@
/*
* Copyright 2021 Paul Schaub.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sop;
import java.io.IOException;
import java.io.OutputStream;
public abstract class Signatures extends Ready {
/**
* Write OpenPGP signatures to the provided output stream.
*
* @param signatureOutputStream output stream
* @throws IOException in case of an IO error
*/
@Override
public abstract void writeTo(OutputStream signatureOutputStream) throws IOException;
}

View file

@ -0,0 +1,30 @@
/*
* Copyright 2021 Paul Schaub.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sop.operation;
import java.io.IOException;
import java.io.InputStream;
import sop.ReadyWithResult;
import sop.Signatures;
public interface DetachInbandSignatureAndMessage {
DetachInbandSignatureAndMessage noArmor();
ReadyWithResult<Signatures> message(InputStream messageInputStream) throws IOException;
}