mirror of
https://github.com/pgpainless/pgpainless.git
synced 2024-11-27 06:42:05 +01:00
Implement signature verification of cleartext-signatures
This commit is contained in:
parent
14ff0e9cc5
commit
225bc78ee1
8 changed files with 611 additions and 0 deletions
|
@ -33,6 +33,8 @@ import org.pgpainless.key.modification.secretkeyring.SecretKeyRingEditor;
|
||||||
import org.pgpainless.key.modification.secretkeyring.SecretKeyRingEditorInterface;
|
import org.pgpainless.key.modification.secretkeyring.SecretKeyRingEditorInterface;
|
||||||
import org.pgpainless.key.parsing.KeyRingReader;
|
import org.pgpainless.key.parsing.KeyRingReader;
|
||||||
import org.pgpainless.policy.Policy;
|
import org.pgpainless.policy.Policy;
|
||||||
|
import org.pgpainless.signature.cleartext_signatures.VerifyCleartextSignatures;
|
||||||
|
import org.pgpainless.signature.cleartext_signatures.VerifyCleartextSignaturesImpl;
|
||||||
import org.pgpainless.symmetric_encryption.SymmetricEncryptorDecryptor;
|
import org.pgpainless.symmetric_encryption.SymmetricEncryptorDecryptor;
|
||||||
import org.pgpainless.util.Passphrase;
|
import org.pgpainless.util.Passphrase;
|
||||||
|
|
||||||
|
@ -100,12 +102,22 @@ public class PGPainless {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a {@link DecryptionStream}, which can be used to decrypt and/or verify data using OpenPGP.
|
* Create a {@link DecryptionStream}, which can be used to decrypt and/or verify data using OpenPGP.
|
||||||
|
*
|
||||||
* @return builder
|
* @return builder
|
||||||
*/
|
*/
|
||||||
public static DecryptionBuilder decryptAndOrVerify() {
|
public static DecryptionBuilder decryptAndOrVerify() {
|
||||||
return new DecryptionBuilder();
|
return new DecryptionBuilder();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify a cleartext-signed message.
|
||||||
|
*
|
||||||
|
* @return builder
|
||||||
|
*/
|
||||||
|
public static VerifyCleartextSignatures verifyCleartextSignedMessage() {
|
||||||
|
return new VerifyCleartextSignaturesImpl();
|
||||||
|
}
|
||||||
|
|
||||||
public static SecretKeyRingEditorInterface modifyKeyRing(PGPSecretKeyRing secretKeys) {
|
public static SecretKeyRingEditorInterface modifyKeyRing(PGPSecretKeyRing secretKeys) {
|
||||||
return new SecretKeyRingEditor(secretKeys);
|
return new SecretKeyRingEditor(secretKeys);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,256 @@
|
||||||
|
/*
|
||||||
|
* 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.File;
|
||||||
|
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.PGPPublicKeyRing;
|
||||||
|
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
||||||
|
import org.bouncycastle.openpgp.PGPSignature;
|
||||||
|
import org.bouncycastle.openpgp.PGPSignatureGenerator;
|
||||||
|
import org.bouncycastle.openpgp.PGPSignatureList;
|
||||||
|
import org.bouncycastle.util.Strings;
|
||||||
|
import org.pgpainless.PGPainless;
|
||||||
|
import org.pgpainless.exception.SignatureValidationException;
|
||||||
|
import org.pgpainless.implementation.ImplementationFactory;
|
||||||
|
import org.pgpainless.signature.SignatureChainValidator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processor for cleartext-signed messages.
|
||||||
|
* Based on Bouncycastle's {@link org.bouncycastle.openpgp.examples.ClearSignedFileProcessor}.
|
||||||
|
*/
|
||||||
|
public class CleartextSignatureProcessor {
|
||||||
|
|
||||||
|
private final ArmoredInputStream in;
|
||||||
|
private final PGPPublicKeyRingCollection verificationKeys;
|
||||||
|
private final MultiPassStrategy multiPassStrategy;
|
||||||
|
|
||||||
|
public CleartextSignatureProcessor(InputStream inputStream,
|
||||||
|
PGPPublicKeyRingCollection verificationKeys,
|
||||||
|
MultiPassStrategy multiPassStrategy)
|
||||||
|
throws IOException {
|
||||||
|
if (inputStream instanceof ArmoredInputStream) {
|
||||||
|
this.in = (ArmoredInputStream) inputStream;
|
||||||
|
} else {
|
||||||
|
this.in = new ArmoredInputStream(inputStream);
|
||||||
|
}
|
||||||
|
this.verificationKeys = verificationKeys;
|
||||||
|
this.multiPassStrategy = multiPassStrategy;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unpack the message from the ascii armor and process the signature.
|
||||||
|
* This method only returns the signature, if it is correct.
|
||||||
|
*
|
||||||
|
* After the message has been processed, the content can be retrieved from the {@link MultiPassStrategy}.
|
||||||
|
* If an {@link InMemoryMultiPassStrategy} was used, the message can be accessed via {@link InMemoryMultiPassStrategy#getBytes()}.
|
||||||
|
* If {@link MultiPassStrategy#writeMessageToFile(File)} was used, the message content was written to the given file.
|
||||||
|
*
|
||||||
|
* @return validated signature
|
||||||
|
* @throws IOException if the signature cannot be read.
|
||||||
|
* @throws PGPException if the signature cannot be initialized.
|
||||||
|
* @throws SignatureValidationException if the signature is invalid.
|
||||||
|
*/
|
||||||
|
public PGPSignature process() throws IOException, PGPException {
|
||||||
|
if (!in.isClearText()) {
|
||||||
|
throw new IllegalStateException("Message is not cleartext.");
|
||||||
|
}
|
||||||
|
|
||||||
|
OutputStream out = new BufferedOutputStream(multiPassStrategy.getMessageOutputStream());
|
||||||
|
|
||||||
|
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;
|
||||||
|
for (PGPPublicKeyRing ring : verificationKeys) {
|
||||||
|
signingKey = ring.getPublicKey(signature.getKeyID());
|
||||||
|
if (signingKey != null) {
|
||||||
|
signingKeyRing = ring;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (signingKey == null) {
|
||||||
|
throw new SignatureValidationException("Missing public key " + Long.toHexString(signature.getKeyID()));
|
||||||
|
}
|
||||||
|
|
||||||
|
signature.init(ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider(), signingKey);
|
||||||
|
|
||||||
|
InputStream sigIn = new BufferedInputStream(multiPassStrategy.getMessageInputStream());
|
||||||
|
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();
|
||||||
|
|
||||||
|
SignatureChainValidator.validateSignature(signature, signingKeyRing, PGPainless.getPolicy());
|
||||||
|
return signature;
|
||||||
|
}
|
||||||
|
|
||||||
|
private 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 == ' ';
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* 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.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
|
||||||
|
public class InMemoryMultiPassStrategy implements MultiPassStrategy {
|
||||||
|
|
||||||
|
private final ByteArrayOutputStream cache = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ByteArrayOutputStream getMessageOutputStream() {
|
||||||
|
return cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ByteArrayInputStream getMessageInputStream() {
|
||||||
|
return new ByteArrayInputStream(cache.toByteArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getBytes() {
|
||||||
|
return getMessageOutputStream().toByteArray();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
/*
|
||||||
|
* 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.ByteArrayOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
public interface MultiPassStrategy {
|
||||||
|
|
||||||
|
OutputStream getMessageOutputStream() throws IOException;
|
||||||
|
|
||||||
|
InputStream getMessageInputStream() throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write the message content out to a file and re-read it to verify signatures.
|
||||||
|
* This strategy is best suited for larger messages (eg. plaintext signed files) which might not fit into memory.
|
||||||
|
* After the message has been processed completely, the messages content are available at the provided file.
|
||||||
|
*
|
||||||
|
* @param file target file
|
||||||
|
* @return strategy
|
||||||
|
*/
|
||||||
|
static MultiPassStrategy writeMessageToFile(File file) {
|
||||||
|
|
||||||
|
return new MultiPassStrategy() {
|
||||||
|
@Override
|
||||||
|
public OutputStream getMessageOutputStream() throws IOException {
|
||||||
|
if (!file.exists()) {
|
||||||
|
boolean created = file.createNewFile();
|
||||||
|
if (!created) {
|
||||||
|
throw new IOException("New file '" + file.getAbsolutePath() + "' was not created.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new FileOutputStream(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream getMessageInputStream() throws IOException {
|
||||||
|
if (!file.exists()) {
|
||||||
|
throw new IOException("File '" + file.getAbsolutePath() + "' does no longer exist.");
|
||||||
|
}
|
||||||
|
return new FileInputStream(file);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read the message content into memory.
|
||||||
|
* This strategy is best suited for small messages which fit into memory.
|
||||||
|
* After the message has been processed completely, the message content can be accessed by calling
|
||||||
|
* {@link ByteArrayOutputStream#toByteArray()} on {@link #getMessageOutputStream()}.
|
||||||
|
*
|
||||||
|
* @return strategy
|
||||||
|
*/
|
||||||
|
static InMemoryMultiPassStrategy keepMessageInMemory() {
|
||||||
|
return new InMemoryMultiPassStrategy();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
/*
|
||||||
|
* 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.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import org.bouncycastle.openpgp.PGPException;
|
||||||
|
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||||
|
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
||||||
|
|
||||||
|
public interface VerifyCleartextSignatures {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provide the {@link InputStream} which contains the cleartext-signed message.
|
||||||
|
* @param inputStream inputstream
|
||||||
|
* @return api handle
|
||||||
|
*/
|
||||||
|
WithStrategy onInputStream(InputStream inputStream);
|
||||||
|
|
||||||
|
interface WithStrategy {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provide a {@link MultiPassStrategy} which is used to store the message content.
|
||||||
|
* Since cleartext-signed messages cannot be processed in one pass, the message has to be passed twice.
|
||||||
|
* Therefore the user needs to decide upon a strategy where to cache/store the message between the passes.
|
||||||
|
* This could be {@link MultiPassStrategy#writeMessageToFile(File)} or {@link MultiPassStrategy#keepMessageInMemory()},
|
||||||
|
* depending on message size and use-case.
|
||||||
|
*
|
||||||
|
* @param multiPassStrategy strategy
|
||||||
|
* @return api handle
|
||||||
|
*/
|
||||||
|
VerifyWith withStrategy(MultiPassStrategy multiPassStrategy);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
interface VerifyWith {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pass in the verification key ring.
|
||||||
|
*
|
||||||
|
* @param publicKey verification key
|
||||||
|
* @return processor
|
||||||
|
* @throws PGPException if the keys cannot be converted to a {@link PGPPublicKeyRingCollection}.
|
||||||
|
* @throws IOException if the keys cannot be parsed properly
|
||||||
|
*/
|
||||||
|
CleartextSignatureProcessor verifyWith(PGPPublicKeyRing publicKey) throws PGPException, IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pass in the verification key ring collection.
|
||||||
|
*
|
||||||
|
* @param publicKeys verification keys
|
||||||
|
* @return processor
|
||||||
|
* @throws IOException if the keys cannot be parsed properly
|
||||||
|
*/
|
||||||
|
CleartextSignatureProcessor verifyWith(PGPPublicKeyRingCollection publicKeys) throws IOException;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
/*
|
||||||
|
* 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.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
import org.bouncycastle.openpgp.PGPException;
|
||||||
|
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||||
|
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
||||||
|
|
||||||
|
public class VerifyCleartextSignaturesImpl implements VerifyCleartextSignatures {
|
||||||
|
|
||||||
|
private InputStream inputStream;
|
||||||
|
private MultiPassStrategy multiPassStrategy;
|
||||||
|
private PGPPublicKeyRingCollection verificationKeys;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WithStrategy onInputStream(InputStream inputStream) {
|
||||||
|
VerifyCleartextSignaturesImpl.this.inputStream = inputStream;
|
||||||
|
return new WithStrategyImpl();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class WithStrategyImpl implements WithStrategy {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public VerifyWith withStrategy(MultiPassStrategy multiPassStrategy) {
|
||||||
|
if (multiPassStrategy == null) {
|
||||||
|
throw new NullPointerException("MultiPassStrategy cannot be null.");
|
||||||
|
}
|
||||||
|
VerifyCleartextSignaturesImpl.this.multiPassStrategy = multiPassStrategy;
|
||||||
|
return new VerifyWithImpl();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class VerifyWithImpl implements VerifyWith {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CleartextSignatureProcessor verifyWith(PGPPublicKeyRing publicKey) throws PGPException, IOException {
|
||||||
|
VerifyCleartextSignaturesImpl.this.verificationKeys = new PGPPublicKeyRingCollection(Collections.singleton(publicKey));
|
||||||
|
return new CleartextSignatureProcessor(inputStream, verificationKeys, multiPassStrategy);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CleartextSignatureProcessor verifyWith(PGPPublicKeyRingCollection publicKeys) throws IOException {
|
||||||
|
VerifyCleartextSignaturesImpl.this.verificationKeys = publicKeys;
|
||||||
|
return new CleartextSignatureProcessor(inputStream, verificationKeys, multiPassStrategy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 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.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Classes related to cleartext signature verification.
|
||||||
|
*/
|
||||||
|
package org.pgpainless.signature.cleartext_signatures;
|
|
@ -0,0 +1,74 @@
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
import org.bouncycastle.openpgp.PGPException;
|
||||||
|
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||||
|
import org.bouncycastle.openpgp.PGPSignature;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.pgpainless.PGPainless;
|
||||||
|
import org.pgpainless.key.TestKeys;
|
||||||
|
import org.pgpainless.signature.cleartext_signatures.CleartextSignatureProcessor;
|
||||||
|
import org.pgpainless.signature.cleartext_signatures.InMemoryMultiPassStrategy;
|
||||||
|
import org.pgpainless.signature.cleartext_signatures.MultiPassStrategy;
|
||||||
|
|
||||||
|
public class CleartextSignatureVerificationTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void cleartextSignVerification() throws IOException, PGPException {
|
||||||
|
String message = "Ah, Juliet, if the measure of thy joy\n" +
|
||||||
|
"Be heaped like mine, and that thy skill be more\n" +
|
||||||
|
"To blazon it, then sweeten with thy breath\n" +
|
||||||
|
"This neighbor air, and let rich music’s tongue\n" +
|
||||||
|
"Unfold the imagined happiness that both\n" +
|
||||||
|
"Receive in either by this dear encounter.\n";
|
||||||
|
String signed = "-----BEGIN PGP SIGNED MESSAGE-----\n" +
|
||||||
|
"Hash: SHA512\n" +
|
||||||
|
"\n" +
|
||||||
|
"Ah, Juliet, if the measure of thy joy\n" +
|
||||||
|
"Be heaped like mine, and that thy skill be more\n" +
|
||||||
|
"To blazon it, then sweeten with thy breath\n" +
|
||||||
|
"This neighbor air, and let rich music’s tongue\n" +
|
||||||
|
"Unfold the imagined happiness that both\n" +
|
||||||
|
"Receive in either by this dear encounter.\n" +
|
||||||
|
"-----BEGIN PGP SIGNATURE-----\n" +
|
||||||
|
"\n" +
|
||||||
|
"iHUEARMKAB0WIQRPZlxNwsRmC8ZCXkFXNuaTGs83DAUCYJ/x5gAKCRBXNuaTGs83\n" +
|
||||||
|
"DFRwAP9/4wMvV3WcX59Clo7mkRce6iwW3VBdiN+yMu3tjmHB2wD/RfE28Q1v4+eo\n" +
|
||||||
|
"ySNgbyvqYYsNr0fnBwaG3aaj+u5ExiE=\n" +
|
||||||
|
"=Z2SO\n" +
|
||||||
|
"-----END PGP SIGNATURE-----";
|
||||||
|
PGPPublicKeyRing signingKeys = TestKeys.getEmilPublicKeyRing();
|
||||||
|
|
||||||
|
InMemoryMultiPassStrategy multiPassStrategy = MultiPassStrategy.keepMessageInMemory();
|
||||||
|
CleartextSignatureProcessor processor = PGPainless.verifyCleartextSignedMessage()
|
||||||
|
.onInputStream(new ByteArrayInputStream(signed.getBytes(StandardCharsets.UTF_8)))
|
||||||
|
.withStrategy(multiPassStrategy)
|
||||||
|
.verifyWith(signingKeys);
|
||||||
|
|
||||||
|
PGPSignature signature = processor.process();
|
||||||
|
|
||||||
|
assertEquals(signature.getKeyID(), signingKeys.getPublicKey().getKeyID());
|
||||||
|
assertArrayEquals(message.getBytes(StandardCharsets.UTF_8), multiPassStrategy.getBytes());
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue