mirror of
https://github.com/pgpainless/pgpainless.git
synced 2024-12-23 11:27:57 +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.parsing.KeyRingReader;
|
||||
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.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.
|
||||
*
|
||||
* @return builder
|
||||
*/
|
||||
public static DecryptionBuilder decryptAndOrVerify() {
|
||||
return new DecryptionBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify a cleartext-signed message.
|
||||
*
|
||||
* @return builder
|
||||
*/
|
||||
public static VerifyCleartextSignatures verifyCleartextSignedMessage() {
|
||||
return new VerifyCleartextSignaturesImpl();
|
||||
}
|
||||
|
||||
public static SecretKeyRingEditorInterface modifyKeyRing(PGPSecretKeyRing 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